require "Logic/Config"
require "proto/gm/message/ReqGmCommandMessage"

local type = type

-- 0 - disable debug info, 1 - less debug info, 2 - verbose debug info
local DEBUG = 3
local PRINT = print
local str = ""
local distanceY = 10;
local languageType = nil
local tableMessageLimit = {180209,180202}--180209-ReqUnionInfoMessage,180202-ReqUnionInfoListMessage
local lastMessageId = 0;
local lastMessageTime = 0;
local M31 = { 1, 3, 5, 7, 8, 10, 12 };
local _uiName = "";

function print(...)
    if DEBUG > 2 then
        str = ""
        for i = 1, select('#', ...) do
            str = str .. tostring(select(i, ...)) .. '\t'
        end
        PRINT(str .. debug.traceback(""))
    elseif DEBUG > 1 then
        PRINT(...)
    end
end

function printError(...)
    if Config.LogLevel > 4 then
        return
    end

    str = ""
    for i = 1, select('#', ...) do
        str = str .. tostring(select(i, ...)) .. '\t'
    end
    Util.LogError(str .. debug.traceback(""), false)
end
local color_r = 0
local color_g = 0
local color_b = 0
function printColor(color, ...)
    if color == nil then
        color_r = 255
        color_g = 0
        color_b = 0
    else
        color_r = color["r"] or 255
        color_g = color["g"] or 0
        color_b = color["b"] or 0
    end
    print(string.format("<color=#%02x%02x%02x>\t", color_r, color_g, color_b) .. table.concat({ ... }) .. "\t</color>")
end

local function dump_value_(v)
    if type(v) == "string" then
        v = "\"" .. v .. "\""
    end
    return tostring(v)
end

function dump(value, desciption, nesting)
    if DEBUG < 1 then
        return
    end
    if type(nesting) ~= "number" then
        nesting = 5
    end

    local lookupTable = {}
    local result = {}

    local traceback = string.split(debug.traceback("", 2), "\n")
    print("dump from: " .. string.trim(traceback[3]))

    local function dump_(value, desciption, indent, nest, keylen)
        local spc = ""
        if type(keylen) == "number" then
            spc = string.rep(" ", keylen - string.len(dump_value_(desciption)))
        end
        if type(value) ~= "table" then
            result[#result + 1] = string.format("%s%s%s = %s", indent, dump_value_(desciption), spc, dump_value_(value))
        elseif lookupTable[tostring(value)] then
            result[#result + 1] = string.format("%s%s%s = *REF*", indent, dump_value_(desciption), spc)
        else
            lookupTable[tostring(value)] = true
            if nest > nesting then
                result[#result + 1] = string.format("%s%s = *MAX NESTING*", indent, dump_value_(desciption))
            else
                result[#result + 1] = string.format("%s%s = {", indent, dump_value_(desciption))
                local indent2 = indent .. "    "
                local keys = {}
                local keylen = 0
                local values = {}
                for k, v in pairs(value) do
                    keys[#keys + 1] = k
                    local vk = dump_value_(k)
                    local vkl = string.len(vk)
                    if vkl > keylen then
                        keylen = vkl
                    end
                    values[k] = v
                end
                table.sort(keys, function(a, b)
                    if type(a) == "number" and type(b) == "number" then
                        return a < b
                    else
                        return tostring(a) < tostring(b)
                    end
                end)
                for i, k in ipairs(keys) do
                    dump_(values[k], k, indent2, nest + 1, keylen)
                end
                result[#result + 1] = string.format("%s}", indent)
            end
        end
    end

    desciption = "<color=#ffff00>" .. (desciption or "dump数据在这里") .. "</color>"
    dump_(value, desciption, "- ", 1)

    local str = ""
    for i, line in ipairs(result) do
        str = str .. line .. "\n"
    end
    print(str)
end

function string.trim(input)
    input = string.gsub(input, "^[ \t\n\r]+", "")
    return string.gsub(input, "[ \t\n\r]+$", "")
end
--限制字长
function string.limitstr(inputStr, limitCount)
    local len = string.len(inputStr)
    if len <= limitCount then
        return inputStr
    end
    local left = len
    local cnt = 0
    local arr = { 0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc }
    while left ~= 0 do
        local tmp = string.byte(inputStr, -left)
        local i = #arr
        while arr[i] do
            if tmp >= arr[i] then
                left = left - i
                break
            end
            i = i - 1
        end
        cnt = cnt + 1
        if cnt >= limitCount then
            return string.sub(inputStr, 0, len - left)
        end
    end
    return inputStr
end
-- 计算 UTF8 字符串的长度，每一个中文算一个字长度
function string.lenUtf8(input)
    local len = string.len(input)
    local left = len
    local cnt = 0
    local arr = { 0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc }
    while left ~= 0 do
        local tmp = string.byte(input, -left)
        local i = #arr
        while arr[i] do
            if tmp >= arr[i] then
                left = left - i
                break
            end
            i = i - 1
        end
        cnt = cnt + 1
    end
    return cnt
end


--输出日志--
function log(str)
    if Config.LogLevel <= 1 then
        Util.Log(str);
    end
end
--输出日志--
function logTrace(str)
    log(tostring(str) .. debug.traceback(""));
end
--错误日志--
function logError(str)
    --Util.LogError(str);
    if Config.LogLevel <= 3 then
        str = '[' .. Time.time .. ']' .. str;
        Util.LogError(str);
    end
end

--警告日志--
function logWarn(str)
    if Config.LogLevel <= 2 then
        Util.LogWarning(str);
    end
end

--log
local json = require "cjson";

function tostringTbl1(tbl)

    local tmp = {}
    for k, v in pairs(tbl) do
        tmp[k] = type(v)

        --table.insert(tmp, k);
    end

    --Util.LogError('tostringTbl1, ' .. json.encode(tmp));

    return json.encode(tmp);
end

function tostringMy(v)
    local str = '';

    str = type(v);
    if type(v) == 'userdata' then
        str = v:ToString();
        --str = type(v);
    elseif type(v) == 'table' then

        local isOk, rt = pcall(json.encode, v);
        if isOk then
            str = rt;
        else
            --Util.LogError('json.encode error, ' .. str);
            str = tostringTbl1(v);
        end

    else
        str = json.encode(v);
    end

    return str;
end

function logMy(...)
    if Config.LogLevel > 4 then
        return
    end

    --local str = '';
    --

    --
    ----local table = require('table')
    ----local args = table.pack(...)
    ----for i = i, args.n do
    ----    local v = args[i];
    ----    local v0 = tostringMy(v);
    ----    str = str .. '    ' .. v0;
    ----end
    --
    ----Util.Log(str);
    --Util.LogError(str);


    local str = ""
    for i = 1, select('#', ...) do
        local v0 = tostringMy(select(i, ...));
        str = table.concat({ str, v0, '\t' })
    end
    Util.LogError(str .. debug.traceback(""), false)
end


--查找对象--
function find(str)
    return GameObject.Find(str);
end

function destroy(obj)
    if HelperFunc.IsNull(obj) then
        return
    end
    GameObject.Destroy(obj);
end
function destroyGoWraper(obj)
    if HelperFunc.IsNull(obj) then
        return
    end
    obj:Dispose()
end

function destroyImmediate(obj)
    GameObject.DestroyImmediate(obj);
end
---@return GameObject
function newObject(prefab)
    return GameObject.Instantiate(prefab);
end
--创建FGUI组件
---@return GComponent
function newGComponent(objName, packName)
    return UIPackage.CreateObject(packName, objName);
end

function child(str)
    return transform:FindChild(str);
end

function subGet(childNode, typeName)
    return child(childNode):GetComponent(typeName);
end

function findPanel(str)
    local obj = find(str);
    if obj == nil then
        error(str .. " is null");
        return nil;
    end
    return obj:GetComponent("BaseLua");
end

-- 清除child
function clearChild(com, bool)
    if com == nil then
        return ;
    end
    local count = com.transform.childCount;

    if count == 0 then
        return ;
    end

    for i = 0, count - 1 do
        local child = com.transform:GetChild(i);
        if bool then
            destroy(child.gameObject);
        else
            child.gameObject:SetActive(false);
        end
    end

end

function isIntersect(rect1, rect2)
    local pos1 = rect1.position;
    local pos2 = rect2.position;

    local halfSumW = (rect1.width * 0.5) + (rect2.width * 0.5);
    local halfSumH = (rect1.height * 0.5) + (rect2.height * 0.5);
    local distanceX = Mathf.Abs(pos1.x - pos2.x);
    local distanceY = Mathf.Abs(pos1.y - pos2.y);

    if (distanceX <= halfSumW and halfSumH >= distanceY) then
        return true
    else
        return false;
    end

end
function isIntersectWithGlobal(item1, parent1, item2, parent2)
    local pos1 = parent1:LocalToGlobal(Vector2(item1.position.x, item1.position.y))
    local pos2 = parent2:LocalToGlobal(Vector2(item2.position.x, item2.position.y))

    local halfSumW = (item1.width * 0.5) + (item2.width * 0.5);
    local halfSumH = (item1.height * 0.5) + (item2.height * 0.5);
    local distanceX = Mathf.Abs(pos1.x - pos2.x);
    local distanceY = Mathf.Abs(pos1.y - pos2.y);

    if (distanceX <= halfSumW and halfSumH >= distanceY) then
        return true
    else
        return false;
    end
end

-- 检测点是否在某区域内
---@param rect {x: number, y: number, width: number, height: number} 检测区域（左上(0, 0)）
---@param point {x: number, y: number} 点
---@return boolean
function hitTestPoint(rect, point)
    local minX = rect.x
    local maxX = rect.x + rect.width
    local minY = rect.y
    local maxY = rect.y + rect.height

    if point.x >= minX and point.x <= maxX and
        point.y >= minY and point.y <= maxY then
        return true 
    end

    return false
end

--对象池(索引，对象池，实例化物体，父物体)
function poolObject(index, pool_select, _obj, _list)
    local _go;
    if index > #pool_select then
        --print("重新创建");
        _go = newObject(_obj.gameObject);
        --加入对象池
        table.insert(pool_select, _go);
    else
        --print("提取对象池");
        _go = pool_select[index];
    end
    _go.gameObject:SetActive(true);
    _go.transform:SetParent(_list);
    Util.Lua_SetLocalScaleXYZ(_go.transform, 1, 1, 1)
    return _go;
end
local timerId;
function LoadAudio(uipanel)
    -- local str = string.split(uipanel,"/")[2]
    -- local value = nil;
    -- local config = G.dataTable["t_bgm"];
    -- for _,v in pairs(config) do
    --     if str == v.f_UIPrefab and str ~= "UIStory" then
    --         value = v;
    --         break;
    --     end
    -- end
    -- if value then
    --     if timerId then
    --         utimer.remove(timerId)
    --     end
    --     if value.f_MusicIntro and value.f_MusicIntro ~= "" then
    --         soundMgr:EnabledBg(true);
    --         soundMgr:SetMusicVolume(0.5);
    --         soundMgr:PlayBG("sound/bg/"..value.f_MusicIntro,false);
    --         if value.f_MusicLoop then
    --             timerId = utimer.timeCall(function ()
    --                 soundMgr:PlayBG("sound/bg/"..value.f_MusicLoop,true);
    --             end,value.f_IntroTime,1);
    --         end
    --     else
    --         if value.f_MusicLoop and value.f_MusicLoop ~= "" then
    --             soundMgr:EnabledBg(true);
    --             soundMgr:SetMusicVolume(0.5);
    --             soundMgr:PlayBG("sound/bg/"..value.f_MusicLoop,true);
    --         end
    --     end
    -- end
    -- if str == "UIBattleFail" or str == "UIBattleSuccess" then
    --     soundMgr:EnabledBg(false);
    -- else
    --     soundMgr:EnabledBg(true);
    -- end
end

function AddUIPackage(packageName)
    if UIPackage.GetById(packageName) then
        return
    end
    UIPackage.AddPackage(packageName)
end

function AddUIPackageDepend(packageName, dependPackageName)
    -- 主动增加一个包 必须指明依赖关系，否则这个包将不会进入自动卸载流程
    YanhuaMMO.UIPackageResManager.Instance:AddCustomUIPackageDepend(packageName, dependPackageName)
end

local uiList = {}--先放这吧
--创建面板--
function createPanel(packageName, name, call)
    panelMgr:CreatePanel(name, packageName, call, nil, nil, false)
end
--关闭UI
function DestoryUI(uiPanel)
    -- closeUI(uiPanel)
    local fguiName = GetFGUIName(uiPanel)
    LuaUIMgr.DestroyUIByResPath(fguiName)
    uiList[uiPanel] = nil
    local uiName, _package = getUIName(uiPanel);
    panelMgr:CloseUI(uiName, _package, true);
    local ctrl = CtrlManager.GetCtrl(uiPanel, false);
    if ctrl and ctrl.DestroyBefore then
        ctrl:DestroyBefore()
    end
    if ctrl then
        CtrlManager.RemoveCtrl(uiPanel)
    end

    -- CtrlManager.RemoveCtrl(uiPanel)
    -- FireEvent(Config.EventType.ShowUI, uiPanel, false, true)
end
--显示Ui
function showUI(uiPanel, ...)
    uiList[uiPanel] = 1
    LuaUIMgr.ShowUI(uiPanel)
    local ui = CtrlManager.GetCtrl(uiPanel);
    --LoadAudio(uiPanel);
    if ui ~= nil then
        ui.Init(...);
    end
    _uiName = getUIName(uiPanel);
    logicMgr.AudioManager.PlayBGMOnOpenUI(_uiName)
    logicMgr.GuideMgr.AddTrigger(logicMgr.GuideMgr.TriggerType.FirstOpenUI, _uiName);
    -- FireEvent(Config.EventType.ShowUI, uiPanel, true)
    --logicMgr.GuideMgr.CheckGuide()
    --TAManager.TrackByDictionary("show_panel",{panel_name=uiPanel})
end

--关闭UI
function closeUI(uiPanel, ignoreCloseSubUI)
    uiPanel = GetFGUIName(uiPanel)
    LuaUIMgr.CloseUIByResPath(uiPanel)
    local uiName, _package = getUIName(uiPanel);
    if IsNoDeleteInCity(uiPanel) then
        uiList[uiPanel] = nil
        panelMgr:CloseUI(uiName, _package, false);

        logicMgr.AudioManager.PlayBGMOnCloseUI(uiName)
    else
        DestoryUI(uiPanel)

        logicMgr.AudioManager.PlayBGMOnCloseUI(uiName, true)
    end
    if not ignoreCloseSubUI then
        closeSubUI(uiPanel)
    end
    -- FireEvent(Config.EventType.ShowUI, uiPanel, false)
    --TAManager.TrackByDictionary("close_panel",{panel_name=uiPanel})
end
function closeSubUI(uiPanel)
    uiPanel = getUIName(uiPanel)
    for key, value in pairs(SubPanel) do
        if uiPanel == getUIName(key) then
            for i = 1, #value do
                if not isOpenUI(value[i]) then
                    DestoryUI(GetFGUIName(value[i]))
                end
            end
        end
    end
    --TAManager.TrackByDictionary("close_panel",{panel_name=uiPanel})
end
function closeAllUI()
    local createUIList = panelMgr:GetCreateUIList()
    for i = 0, createUIList.Length - 1 do
        local name = createUIList[i]
        closeUI(name)
    end
    --[[for k, v in pairs(uiList) do
        closeUI(k)
    end]]
    --logError("count:" .. table.getn(uiList))
end

--- 清除界面的同时卸载Lua(谨慎调用)
function ClearAllUI(ignoreCall)
    for uiPanel, _ in pairs(uiList) do
        local name = GetFGUIName(uiPanel)
        if ignoreCall == nil or not ignoreCall(name) then
            DestoryUI(name)
            CtrlManager.RemoveCtrl(name)
        end
    end
end

function destroyAllUI()
    destroyAllUIIgnore()
end
function destroyAllUIIgnore(ignoreCall)
    local createUIList = panelMgr:GetCreateUIList()
    for i = 0, createUIList.Length - 1 do
        local name = createUIList[i]
        if ignoreCall == nil or not ignoreCall(name) then
            DestoryUI(name)
        end
    end
end
function DestoryBattleInUI(time)
    DestoryUI(PanelNames.UIBattleMain)
    DestoryUI(PanelNames.UIFight)
    utimer.delayCall(function()
        DestoryUI(PanelNames.UIBattleFail)
        DestoryUI(PanelNames.UIBattleSuccess)
    end, time)
end

function IsNoDeleteInCity(name)
    if IsNoDelete(name) then
        return true
    end
    for key, value in pairs(SubPanel) do
        if true then
            for i = 1, #value do
                if name == GetFGUIName(value[i]) then
                    return true
                end
            end
        end
    end
    return false
end
function IsNoDelete(name)
    local name = getUIName(name)
    for key, value in pairs(NoDelete) do
        if name == getUIName(key) then
            return true
        end
    end
    return false
end
--是否打开UI
function isOpenUI(uiPanel)
    local uiName = getUIName(uiPanel);
    return panelMgr:IsOpen(uiName);
end

function getUI(uiPanel)
    local uiName = getUIName(uiPanel);
    return panelMgr:GetUI(uiName);
end

-- 缓存UI逻辑
-- 1 除去NoDelete的UI之外最多额外存储M个
local cacheCreateUI = {}
local maxCacheCreateUI = 10
function AddCacheCreateUIAndDestory(name)
    if IsNoDeleteInCity(name) then
        return
    end
    local index = table.containsIndex(cacheCreateUI, name)
    if index > 0 then
        table.remove(cacheCreateUI, index)
    end
    table.insert(cacheCreateUI, name)

    DestoryCacheCreateUI()
end
function DestoryCacheCreateUI()
    local count = #cacheCreateUI
    if count <= maxCacheCreateUI then
        return
    end
    for i = 1, (count - maxCacheCreateUI) do
        local name = cacheCreateUI[i]
        if not isOpenUI(name) then
            DestoryUI(name)
        end
    end
end
function RemoveCacheCreateUI(name)
    local index = table.containsIndex(cacheCreateUI, name)
    if index > 0 then
        table.remove(cacheCreateUI, index)
    end
end

logicMgr = {}
setmetatable(logicMgr, { __index = function(k, v)
    if rawget(k, v) == nil then
        local manager = require("Logic/" .. v)
        if manager.Init then
            manager.Init()
        elseif manager.init then
            manager.init()
        end
        rawset(k, v, manager)
    end
    return rawget(k, v)
end })
ClientMgr = {}
setmetatable(ClientMgr, { __index = function(k, v)
    if rawget(k, v) == nil then
        local manager = require("ClientFight/xiaoniu/fszx/ClientFightDefine")
        --if manager.Init then
        --    manager.Init()
        --elseif manager.init then
        --    manager.init()
        --end
        rawset(k, v, manager)
    end
    return rawget(k, v)
end })


local mt = {}

function selectClass(clsName, base)
    local cls = {}
    base = base or mt
    cls.__get__ = {}
    cls.__set__ = {}
    setmetatable(cls, {__index = base})
    cls.clsName = clsName or "default"
    cls.base = base
    cls.new = function(...)
        local cls_instance = {}
        cls_instance.getset_values = {}
        for k,v in pairs(cls) do
            cls_instance[k] = v
        end
        local cls_instance_mt = {
            __index = function(t, k)
                if cls[k] then
                    return cls[k]
                end
                if t.__get__[k] then
                    t.__get__[k](t,k)
                    return t.getset_values[k]
                end
                if string.sub(k, 1, 2) == "__" then
                    local tmpK = string.sub(k, 3, -1)
                    if t.getset_values[tmpK] then
                        return t.getset_values[tmpK]
                    end
                end
            end,
            __newindex = function(t, k, v)
                if t.__set__[k] then
                    t.__set__[k](t,k, v)
                    cls_instance.getset_values[k] = v
                    return
                end
                if string.sub(k, 1, 2) == "__" then
                    local tmpK = string.sub(k, 3, -1)
                    if t.getset_values[tmpK] then
                        t.getset_values[tmpK] = v
                    end
                end
                rawset(t, k, v)
            end
        }
        setmetatable(cls_instance, cls_instance_mt)
        if cls_instance.onCreate then
            cls_instance:onCreate(...)
        end
        return cls_instance
    end
    return cls
end


--服务端富文本格式转化 --聊天
function stringFormatColor(str)
    str = string.gsub(tostring(str), '<COL=(.-)|(.-)>', '<color=%1>%2</color>');
    str = string.gsub(str, "/m", "\n");
    str = string.gsub(str, "0x", "#");

    return str;
end

--物品打造tips专用"合成成功，获得『<ID=0|-1|治疗药剂>』*1。"
function stringFormatTips(str)

    local colorid = string.gsub(str, "(.-)<ID=(.-)|(.-)|(.-)>(.+)", "%2");

    colorid = tonumber(colorid);-- Util.IntTo16Hex(tonumber(#colorid < 12 and colorid or 16777215));

    colorid = ZZMathBit.rShiftOp(colorid, 24);
    local color = G.dataTable["ItemQualityConfig"]["ColorCode"][tonumber(colorid)];
    if color == nil then
        return str;
    end

    color = string.gsub(color, "0x", "#");
    local str2 = string.gsub(str, "(.+)【<ID=(%d+)|(.+)|(.+)>】(.+)", "%1" .. "<color=" .. color .. ">" .. "%4" .. "</color>" .. "%5");
    return str2;

    --[[    local color = string.gsub(str,"(.-)<ID=(.-)|(.-)|(.-)>(.+)","%2")

    color = Util.IntTo16Hex(tonumber(#color < 12 and color or 16777215)) -- 没有匹配到直接显示白色，防止报错
    color = SetColorRGB(color)
    return color;]]
end
function SetColorRGB(color)
    local _color
    if #color == 8 then
        _color = string.sub(color, 3, string.len(color))
    else
        _color = color
    end
    return _color
end
--打印luatable
function printTable(tab)
    local json = require "cjson"
    msgJson = json.encode(tab);
    print(msgJson);
end

--分割字符串 1.待分割字符串 2.分隔符,3 table 列表
function string_split(str, split_char, tbList)
    local sub_str_tab = tbList or {};
    while (true) do
        local pos = string.find(str, split_char);
        if (not pos) then
            sub_str_tab[#sub_str_tab + 1] = str;
            break ;
        end
        local sub_str = string.sub(str, 1, pos - 1);
        sub_str_tab[#sub_str_tab + 1] = sub_str;
        str = string.sub(str, pos + 1, #str);
    end

    return sub_str_tab;

end

--main sub皆为10进制
function getProtocol(mainType, subType)
    local m = string.format("%#x", mainType);
    local s = string.format("%#x", subType);

    m = string.sub(m, 3, #m);
    s = string.sub(s, 3, #s);
    if #s == 1 then
        s = "0" .. s;
    end
    if #m == 1 then
        m = "0" .. m;
    elseif #m == 0 then
        m = "00";
    end

    return "0x" .. m .. s;
end

--时间秒转字符串
function timeToTimeLeft(time, minShow, maxShow, dayShow)
    local d = Mathf.Floor(time / (3600 * 24));
    local h = Mathf.Floor((time % (3600 * 24)) / 3600);
    local m = Mathf.Floor((time % (3600 * 24)) % 3600 / 60);
    local s = Mathf.Floor(time % (3600 * 24) % 3600 % 60)
    if dayShow then
        return string.format("%d天", d)
    end
    if minShow then
        if d <= 0 and h <= 0 then
            return string.format("%d分%d秒", m, s)
        end

        if d <= 0 then
            return string.format("%d小时%d分%d秒", h, m, s)
        end
    end

    if maxShow then
        return string.format("%d天%d小时", d, h);

    end

    return string.format("%d天%d小时%d分%d秒", d, h, m, s);
end

--时间秒转换成字符串标准格式:三级格式(不显示天数)
function timeToTimeFormat(time)
    local h = math.floor((time) / 3600);
    local m = math.floor((time - 3600 * h) / 60);
    local s = math.floor(time - 3600 * h - 60 * m);
    if h < 10 then
        h = "0" .. h -- body
    end
    if m < 10 then
        m = "0" .. m -- body
    end
    if s < 10 then
        s = "0" .. s -- body
    end
    return string.format("%s", h .. ":" .. m .. ":" .. s);
end

function timeToTimeFormat2(time)
    local h = math.floor((time) / 3600);
    local m = math.floor((time - 3600 * h) / 60);
    local s = math.floor(time - 3600 * h - 60 * m);
    if h < 10 then
        h = "0" .. h -- body
    end
    if m < 10 then
        m = "0" .. m -- body
    end
    if s < 10 then
        s = "0" .. s -- body
    end
    return h, m, s
end

function TimeFormatToHHMMSS(time)
    local h = math.floor((time) / 3600);
    local m = math.floor((time - 3600 * h) / 60);
    local s = math.floor(time - 3600 * h - 60 * m);
    if h < 10 then
        h = "0" .. h -- body
    end
    if m < 10 then
        m = "0" .. m -- body
    end
    if s < 10 then
        s = "0" .. s -- body
    end
    return string.format("%s", h .. ":" .. m .. ":" .. s);
end

--时间秒转换成字符串标准格式:二级格式
function timeToTimeFormat_MS(time)
    local m = math.floor(time / 60);
    local s = math.floor(time - 60 * m);
    if m < 10 then
        m = "0" .. m -- body
    end
    if s < 10 then
        s = "0" .. s -- body
    end
    return string.format("%s", m .. ":" .. s);
end
--时间戳转年月日
function formatUnixTime2Date(time)
    if time and time >= 0 then
        local tb = {}
        tb.year = tonumber(os.date("%Y", time))
        tb.month = tonumber(os.date("%m", time))
        tb.day = tonumber(os.date("%d", time))
        tb.hour = tonumber(os.date("%H", time))
        tb.minute = tonumber(os.date("%M", time))
        tb.second = tonumber(os.date("%S", time))
        return tb;
    end
end
--时间戳格式化(毫秒)
function TimeFormateByMs(time)
    if time and time >= 0 then
        time = time / 1000
        local _timeStr = os.date("%Y-%m-%d %H:%M:%S", time)
        return _timeStr
    end
    return ""
end

function hideCanvasUI(canvas, closeAll)
    local count = canvas.transform.childCount;
    for i = 0, count - 1 do
        local child = canvas.transform:GetChild(i);
        local find = NoDelete[child.name];
        if not find and child.gameObject.activeSelf or closeAll then
            --挂机收益界面不能关闭 fuck
            if child.name ~= "UIOfflineRewards" and child.name ~= "UIBossRewards" and child.name ~= "UIRoundBattle" and child.name ~= "UIScreenEffect" and child.name ~= "UINotice" then
                panelMgr:CloseUI(child.name, false);
            end
        end
    end
end

--界面切换隐藏已打开UI 参数closeAll关闭所有
function hideOtherUI(closeAll)
    -- DestoryUI(PanelNames.UILogin);
    -- DestoryUI(PanelNames.UICreateName);
    -- hideCanvasUI(MainCanvas, closeAll);
    -- hideCanvasUI(MainTopCanvas, closeAll);
    -- hideCanvasUI(PopCanvas, closeAll);
    -- hideCanvasUI(PopTopCanvas, closeAll);
    -- hideCanvasUI(MsgCanvas, closeAll);
    -- hideCanvasUI(TopCanvas, closeAll);
end
local cacheGetUINames = {}
function getUIName(PanelNames)
    local cache = cacheGetUINames[PanelNames]
    if not cache then
        cache = string_split(PanelNames, "/")
        cacheGetUINames[PanelNames] = cache
    end
    local name = cache--string.split(PanelNames, "/");
    local uiName = name[#name];
    local _package = "FUI/" .. name[1];
    return uiName, _package
end
function getUIName2(PanelNames)
    local cache = cacheGetUINames[PanelNames]
    if not cache then
        cache = string_split(PanelNames, "/")
        cacheGetUINames[PanelNames] = cache
    end
    local name = cache
    local uiName = name[#name];
    return uiName
end
--文字上色
function string.TextColor(text, color)
    return string.format("[color=%s]%s[/color]", color, text);
end

function table.contains(tab, value)
    if type(tab) ~= "table" then
        return false
    end
    for k, v in pairs(tab) do
        if v == value then
            return true
        end
    end
    return false
end
function table.containsIndex(tab, value)
    if tab == nil then
        return 0
    end
    if type(tab) ~= "table" then
        return 0
    end
    for k, v in pairs(tab) do
        if v == value then
            return k
        end
    end
    return 0
end

function table.getn(table)
    --table key非递增情况下使用
    if table ~= nil then
        local index = 0
        for k, v in pairs(table) do
            index = index + 1
        end
        return index
    end
    return 0
end
function table.ArrayRemoveByValue(t, value)
    for k, v in pairs(t) do
        if v == value then
            table.remove(t, k)
            break
        end
    end
end
function table.removeByValue(t, value)
    for i = table.getn(t), 1, -1 do
        if t[i] == nil then
            break
        end
        if t[i] == value then
            table.remove(t, i)
            return i
        end
    end
    return 0
end
function table.isNullOrEmpty(t)
    if t == nil or type(t) ~= "table" then
        return true
    end
    return table.getn(t) == 0
end
function table.findIndexByJudge(t, judge)
    local index = -1
    for i, v in ipairs(t) do
        if judge(v) then
            index = i
            break
        end
    end
    return index
end

function table.removeIndexByJudge(t, judge)
    local index = table.findIndexByJudge(t, judge)
    if index > 0 and index <= #t then
        table.remove(t, index)
    end
end

--- 返回数组某个值的索引
---@generic T
---@param list  T[] 数组
---@param value T   值
---@return number 索引（0表示不存在）
function table.indexOf(list, value)
    if list == nil then
        return 0
    end
    for i = 1, #list do
        if list[i] == value then
            return i
        end
    end
    return 0
end

function playSoundDefault(index)
    -- local path = G.dataTable["Global"][tostring(index)]["FunParameter"];
    -- if path ~= nil then
    --     soundMgr:Play(path, nil)
    -- end
end
-- ---播放技能音效 step： 1:聚气 2:飞行 3:击中
-- function playSkillSoundById(id, step)
--     local path = { "preSound", "flySound", "hitSound" }
--     local val = GetStaticData("SkillRound", path[step], tonumber(id));
--     if val == nil or #val == 0 then
--         return
--     end
--     local soundPath = "sound/skill/" .. val
--     soundMgr:Play(soundPath, nil)
-- end

-- ---播放技能音效
-- function playSkillSoundByName(name)
--     if name == nil or #name == 0 then
--         return
--     end
--     local soundPath = "sound/fight/" .. name
--     soundMgr:Play(soundPath, nil)
-- end

-- ---播放CV
-- function playCVSoundByName(name)
--     if name == nil or #name == 0 then
--         return
--     end
--     local soundPath = "sound/cv/" .. name
--     soundMgr:PlayCV(soundPath, nil)
-- end

-- ---buff生效时播放
-- function playBuffSoundById(id)
--     local data = G.dataTable["BuffData"][tostring(id)]
--     if data == nil then
--         return
--     end
--     local path = data.effectSound
--     if #path > 0 then
--         path = "sound/skill/" .. path
--         soundMgr:Play(path, nil)
--     end
-- end

-- ---播放音效
-- function playCommonByType(value)
--     if value == nil then
--         return
--     end
--     local sound_config;
--     if type(value) == 'string' then

--         local type = ConverStringToEnum(Config.SoundType, value);
--         if type ~= nil then
--             sound_config = G.dataTable["t_sound"][tostring(type)];
--         else
--             log("找不到音效类型:" .. value);
--         end
--     else
--         sound_config = G.dataTable["t_sound"][tostring(value)];
--     end
--     if sound_config ~= nil then
--         local soundname = sound_config.f_music;
--         local soundPath = "sound/common/" .. soundname;
--         soundMgr:Play(soundPath, nil);
--     else
--         log("找不到音效文件:" .. value);
--     end
-- end

--特效开关  是-关 否-开 其他-nil
function isCloseFX(FXname, close, CloseAll)

    local dic = resMgr.s_LoadedEffects:GetEnumerator();
    while dic:MoveNext() do
        local v = dic.Current.Value;
        local k = dic.Current.Key;
        if FXname ~= nil then
            if v.name == FXname then
                v.gameObject:SetActive(close ~= true);
            end
        elseif CloseAll ~= nil then
            v.gameObject:SetActive(CloseAll ~= true);
        end
    end

end

-- function playCommonByName(value)
--     if value == nil then
--         return
--     end
--     local soundname = value;
--     local soundPath = "sound/common/" .. soundname;
--     soundMgr:Play(soundPath, nil);
-- end

function ConverStringToEnum(enum, value)
    for k, v in pairs(enum) do
        if k == value then
            return v;
        end
    end
    return nil;
end

function clone(object)
    if object == nil then
        return nil;
    end
    local lookup_table = {}
    local function _copy(object)
        if type(object) ~= "table" then
            return object
        elseif lookup_table[object] then
            return lookup_table[object]
        end
        local newObject = {}
        lookup_table[object] = newObject
        for key, value in pairs(object) do
            newObject[_copy(key)] = _copy(value)
        end
        return setmetatable(newObject, getmetatable(object))
    end
    return _copy(object)
end

function getTextById(id)
    local sData = G.dataTable[Config.language];
    if sData == nil then
        sData = G.dataTable["MessageTips"];
    end
    local msg = sData.Message[id];
    if msg == nil then
        msg = id;
    end
    return msg;
end

--打印堆栈
function traceback(name)
    return debug.traceback(name, 2)
end

--解析物品可操作行为
function getItemOperate(intNum)
    --从低到高取二进制位0否1是
    local operateList = {}
    operateList["discard"] = bit.band(bit.rshift(intNum, 0), 1);--是否可丢弃0否1是
    operateList["bind"] = bit.band(bit.rshift(intNum, 1), 1);--已绑定
    operateList["sell"] = bit.band(bit.rshift(intNum, 2), 1);--出售 && 分解
    operateList["fold"] = bit.band(bit.rshift(intNum, 3), 1);--堆叠
    operateList["only"] = bit.band(bit.rshift(intNum, 4), 1);--装备穿戴唯一
    operateList["depot"] = bit.band(bit.rshift(intNum, 5), 1);--存仓库
    operateList["moneySell"] = bit.band(bit.rshift(intNum, 6), 1);--金币寄售
    operateList["diamondSell"] = bit.band(bit.rshift(intNum, 7), 1);--金券寄售
    operateList["pkProtect"] = bit.band(bit.rshift(intNum, 8), 1);--pk保护

    return operateList
end

--服务器时间(年;月;日;时;分;秒;周)
function getServerTime()
    if G.serverData["idleNotice"] == nil then
        return nil
    end
    local sTime = G.serverData["idleNotice"]["sysTime"]
    if sTime ~= nil then
        sTime = tostring(sTime)
        return os.date("%Y;%m;%d;%H;%M;%S;%w", sTime / 1000)
    end
end
--输入向量，返回角度
function getAngleByXY(x, y)
    local yx = y / x
    local angle = 0
    if x > 0 then
        angle = Mathf.Atan(yx) * 180 / Mathf.PI
    else
        angle = Mathf.Atan(yx) * 180 / Mathf.PI + 180
    end
    return angle
end
--创建DOTween队列
function getDOTweenSequence(dataTable, Ease)
    local s = DG.Tweening.DOTween.Sequence();
    for i = 1, #dataTable do
        if type(dataTable[i]) == "number" then
            s:AppendInterval(dataTable[i]);--添加延时间隔
        elseif type(dataTable[i]) == "function" then
            s:AppendCallback(dataTable[i]);--添加回调函数
        else
            local tween = dataTable[i]
            if Ease ~= nil then
                tween:SetEase(Ease)
            end
            s:Append(tween);--添加DoTween动画
        end
    end
    return s;
end
--数字用千分号隔开，满5位数字，用+K、M、B、T字母来缩写表示
function getNumString(num)
    num = num or 0
    return HelperFunc.FormatResourceNum(Mathf.Round(tonumber(tostring(num))))
    --[[local i=1;
    local dataNum = tonumber(num) or 0;
    local dataNumString;
    local unitList = {"","K","M","B","T"}
    while (dataNum/100000>=1) do
        i=i+1;
        if i>5 then
            i=5;
            break;
        end
        dataNum =   math.modf(dataNum/1000)
    end
    if dataNum/1000>=1 then
        dataNumString = math.modf(dataNum/1000) .. string.format("%03d",math.fmod( dataNum,1000 )) .. unitList[i]
    else
        dataNumString = dataNum .. unitList[i]
    end
    return dataNumString
    --]]
end
--获取itemCodeNum
function GetItemCodeNum(configKey)
    local num = 0;
    local numList = {};
    if type(configKey) == "string" then
        if G.serverData["itemCodeNum"][configKey] == nil then
            num = 0;
        else
            num = G.serverData["itemCodeNum"][configKey]
        end
        return num;
    elseif type(configKey) == "table" then
        for i = 1, #configKey do
            local count = 0;
            if G.serverData["itemCodeNum"][configKey[i]] == nil then
                count = 0;
            else
                count = G.serverData["itemCodeNum"][configKey[i]]
            end
            table.insert(numList, count)
        end
        return numList;
    end

end

--change layer
function setLayer(com, layer)
    if com == nil then
        return ;
    end
    com.gameObject.layer = layer;

    local count = com.transform.childCount;
    if count == 0 then
        return
    end
    for i = 0, count - 1 do
        local child = com.transform:GetChild(i);
        setLayer(child, layer)
    end

end

function GetNpc(special)
    if special then
        --佣兵任务使用NPC(7,0)
        return 7, 0
    end
    local MyRace = G.serverData.roleInfo.ability.race
    local NPC_X = MyRace + 2
    local NPC_Y = 0
    return NPC_X, NPC_Y
end

function GetStaticData(tabName, roleKey, colKey)

    local d = G.dataTable[tabName];
    if d then
        return (d[roleKey] or {})[colKey];
    end
    return nil

end

--迭代器,下标不连续时正序排列
function pairsByKeys(t)
    local a = {}
    for n in pairs(t) do
        a[#a + 1] = n
    end
    table.sort(a)
    local i = 0
    return function()
        i = i + 1
        return a[i], t[a[i]]
    end
end

--迭代器,下标不连续时倒序排列
function pairsByKeys2(t)
    local a = {}
    for n in pairs(t) do
        a[#a + 1] = n
    end
    table.sort(a, function(a, b)
        return a > b
    end)
    local i = 0
    return function()
        i = i + 1
        return a[i], t[a[i]]
    end
end

--获得自己的公会名
function getMyGuildName()
    if G.serverData["guild"] ~= nil and G.serverData["guild"]["myPower"] ~= nil then
        return G.serverData["guild"]["myPower"].guildName
    end
    return nil
end

function checkModuleOpen(id, showMsg)

    local rolelevel = 0;
    if G.serverData["roleInfo"] ~= nil then
        rolelevel = G.serverData["roleInfo"].show.level;
    end
    local mapid = 0;
    if G.serverData["mapInfo"] ~= nil then
        mapid = G.serverData["mapInfo"].map_id;
    end

    local table_functionConfig = G.dataTable["T_function"];
    local Config = table_functionConfig[tostring(id)];

    if Config ~= nil then
        if showMsg then
            if rolelevel < Config["f_Lvcon"] then
                UISysTips.AddMsg("等级到" .. Config["f_Lvcon"] .. "解锁")
                return false;
            end
            if mapid < Config["f_mapcon"] then
                UISysTips.AddMsg("关卡到" .. Config["f_mapcon"] .. "解锁")
                return false;
            end
        end

        if rolelevel >= Config["f_Lvcon"] and mapid >= Config["f_mapcon"] then
            return true;
        else
            return false;
        end
    end
    return true;
end

function indexofTable(srcTable, target)

    for i = 1, #srcTable do
        if srcTable[i] == target then
            return i;
        end
    end
    return -1;
end
--talkingdata自定义事件

--根据地图名获得对应阵营颜色
function GetMapNameColor(name)
    local mapName = tostring(name)
    local worldmap = G.dataTable["WorldMap"];
    for k, v in pairs(worldmap) do
        if v.MapName == mapName then
            if v.RaceID == 1 then
                mapName = "<color=#7ACBE9>" .. v.MapName .. "(贝鲁领地)" .. "</color>";
            elseif v.RaceID == 2 then
                mapName = "<color=#E6688E>" .. v.MapName .. "(莫克林领地)" .. "</color>";
            else
                mapName = "<color=#FFF06D>" .. v.MapName .. "</color>";
            end
            break
        end
    end
    return mapName
end

function getStrengthColo(strenglvl)
    if strenglvl < 5 then
        return string.gsub(G.dataTable["ItemQualityConfig"]["ColorCode"][1], "0x", "#");
    elseif strenglvl < 9 then
        return string.gsub(G.dataTable["ItemQualityConfig"]["ColorCode"][2], "0x", "#");
    else
        return string.gsub(G.dataTable["ItemQualityConfig"]["ColorCode"][4], "0x", "#");
    end

end

function getFumoIcon(fumoName)
    if string.find(fumoName, "初级") ~= nil then
        return "fumo1.png";
    elseif string.find(fumoName, "次级") ~= nil then
        return "fumo2.png";
    elseif string.find(fumoName, "强效") ~= nil then
        return "fumo3.png";
    elseif string.find(fumoName, "卓越") ~= nil then
        return "fumo4.png";
    elseif string.find(fumoName, "超级") ~= nil then
        return "fumo5.png";
    end

end

--调用本地化文字
function getLanguage(key, lanType, default)
    if key == nil then
        logError("func getLanguage key is nil");
        if default ~= nil then
            return default;
        end
        return "";
    end
    if key == "" then
        return ""
    end

    languageType = logicMgr.LanguageManager.GetLanguageType()

    local lType = languageType
    if (lanType ~= nil) then
        lType = lanType;
    end
    local tempStr = G.dataTable["t_language"][key] or G.dataTable["t_language_2"][key] or G.dataTable["t_language_story"][key]
    if (tempStr == nil) then
        if default ~= nil then
            return default;
        end
        return "not have thisKey:" .. key
    end
    if (lType == EnumConst.LanguageType.CN) then
        local _TextCN = tempStr.f_TextCN
        local ss = string.gsub(_TextCN, "\\n", "\n")
        return ss
    end
    if (lType == EnumConst.LanguageType.EN) then
        return tempStr.f_TextEN
    end
    if (lType == EnumConst.LanguageType.JP) then
        return tempStr.f_TextJP
    end
    if (lType == EnumConst.LanguageType.CT) then
        return tempStr.f_TextTC
    end
end

--设置语言
function choseLanguage(sendLanguage)
    EnumConst.ChoseLanguage = sendLanguage
end

--获取当前语言类型
function getChoseLanguage()
    --EnumConst.ChoseLanguage = Util.GetPlayerPrefs("Language_Type")
    return EnumConst.ChoseLanguage;
end

function ItemFromJump(jumpId)
    --1：神秘商店钻石标签页UIShop的DiscountBtn
    --2：神秘商店魔晶标签页UIShop的BuffBtn
    --3：地下城UIInstance
    --4：中立任务UIZhongliTask（先弹弹窗询问是否去冒险地区，确定后自动寻路到中立任务NPC并打开中立任务界面
    --5：铁匠铺分解标签页UIBlacksmith的DecomposeBtn
    --6：铁匠铺合成标签页药剂类标签页UIBlacksmith的CompoundBtn的PotionBtn
    --7：铁匠铺合成标签页宝石类标签页UIBlacksmith的CompoundBtn的GemBtn
    --8：铁匠铺合成标签页设计图标签页UIBlacksmith的CompoundBtn的RunesBtn
    --9：铁匠铺合成标签页杂物类标签页UIBlacksmith的CompoundBtn的OtherBtn
    print("物品来源跳转===" .. jumpId)
    if jumpId == 1 then
        UIBottom.onSetHome();
        showUI(PanelNames.UIShop, 1)
    elseif jumpId == 2 then
        UIBottom.onSetHome();
        showUI(PanelNames.UIShop, 2)
    elseif jumpId == 3 then
        UIBottom.onSetHome();
        showUI(PanelNames.UIInstance)
    elseif jumpId == 4 then
        local MyRace = G.serverData.roleInfo.ability.race;
        local mapid;
        if MyRace == 1 then
            if (G.serverData["mapInfo"]["map_id"] == 3001) then
                UIBottom.onSetBattle();
                return ;
            end
            mapid = 3001;
        elseif MyRace == 2 then
            if (G.serverData["mapInfo"]["map_id"] == 3005) then
                UIBottom.onSetBattle();
                return ;
            end
            mapid = 3005;
        end

        local pop = CtrlManager.GetCtrl(PanelNames.UICommonPop);
        pop.Init("57", function(b)
            if b then
                mapMgr:ChangeMapByMapId(mapid);
                UIBottom.onSetBattle();
            end
        end);
    elseif jumpId == 5 then

    elseif jumpId == 6 then
        UIBottom.onSetHome();
        showUI(PanelNames.UIBlacksmith, "compound", "potion");
    elseif jumpId == 7 then
        UIBottom.onSetHome();
        showUI(PanelNames.UIBlacksmith, "compound", "gem")
    elseif jumpId == 8 then
        UIBottom.onSetHome();
        showUI(PanelNames.UIBlacksmith, "compound", "runes")
    elseif jumpId == 9 then
        UIBottom.onSetHome();
        showUI(PanelNames.UIBlacksmith, "compound", "other")
    end
end

function subDigitalNum(digital, num)
    local numstr = tostring(digital);
    local strarr = string_split(numstr, "%.");
    local intstr = strarr[1] .. ".";
    if #strarr > 1 then
        for i = 1, num do
            if string.len(strarr[2]) >= i then
                intstr = intstr .. string.sub(strarr[2], i, i);
            else
                intstr = intstr .. "0";
            end
        end
    else
        intstr = intstr .. "0000";
    end
    return intstr;
end

local json = require "cjson"
local json_safe = require "cjson.safe"

function getJsonStr(tbl)
    json.encode_sparse_array(true);
    local result = json.encode(tbl);

    return result;
end

function decodeJsonStrToTabel(jonsonStr)
    local tt = type(jonsonStr)
    -- 已经解析过了
    if jonsonStr ~= nil and tt == "table" then
        return jonsonStr
    end
    if jonsonStr ~= nil and tt == "number" then
        return jonsonStr
    end

    local r, s = json_safe.decode(jonsonStr);
    return r;
end

function getTimeStr(timesecond)

    local days = Mathf.Floor(timesecond / (3600 * 24));
    local hours = Mathf.Floor((timesecond % (3600 * 24)) / 3600);
    local minutes = Mathf.Floor((timesecond % (3600 * 24)) % 3600 / 60);
    local str = "";
    if days > 0 then
        str = days .. "天";
    end
    if hours > 9 then
        str = str .. hours .. "小时";
    else
        str = str .. "0" .. hours .. "小时";
    end

    if minutes > 9 then
        str = str .. minutes .. "分";
    else
        str = str .. "0" .. minutes .. "分";
    end
    return str;
end

function getTimeStrDHMS(timesecond)

    local days = Mathf.Floor(timesecond / (3600 * 24));
    local hours = Mathf.Floor((timesecond % (3600 * 24)) / 3600);
    local minutes = Mathf.Floor((timesecond % (3600 * 24)) % 3600 / 60);
    local seconds = Mathf.Floor((timesecond % (3600 * 24)) % 3600 % 60);
    if days <= 0 and hours <= 0 and minutes <= 0 then
        if seconds > 9 then
            return seconds .. "秒"
        else
            return "0" .. seconds .. "秒"
        end
    end
    local str = "";
    if days > 0 then
        str = days .. "天";
    end
    if hours > 9 then
        str = str .. hours .. "小时";
    else
        str = str .. "0" .. hours .. "小时";
    end

    if minutes > 9 then
        str = str .. minutes .. "分";
    else
        str = str .. "0" .. minutes .. "分";
    end
    return str;
end

function getTimeStrD_H_M_S(timesecond)

    local days = Mathf.Floor(timesecond / (3600 * 24));
    local hours = Mathf.Floor((timesecond % (3600 * 24)) / 3600);
    local minutes = Mathf.Floor((timesecond % (3600 * 24)) % 3600 / 60);
    local seconds = Mathf.Floor((timesecond % (3600 * 24)) % 3600 % 60);

    if days <= 0 and hours <= 0 and minutes <= 0 then
        if seconds > 9 then
            return seconds .. "秒"
        else
            return "0" .. seconds .. "秒"
        end
    end

    local str = "";
    if days > 0 then
        return days .. "天"
    end
    if hours > 0 then
        if hours > 9 then
            str = str .. hours .. "小时";
        else
            str = str .. "0" .. hours .. "小时";
        end
        return str
    end
    if minutes > 0 then
        if minutes > 9 then
            str = str .. minutes .. "分";
        else
            str = str .. "0" .. minutes .. "分";
        end
        return str
    end
    return ""
end

-- 获取时间字串
function getTimeDayOrHMStr(timesecond)

    local days = Mathf.Floor(timesecond / (3600 * 24));
    local hours = Mathf.Floor((timesecond % (3600 * 24)) / 3600);
    local minutes = Mathf.Floor((timesecond % (3600 * 24)) % 3600 / 60);
    local str = "";
    if days > 0 then
        str = days .. "天";
        return str
    end
    if hours > 9 then
        str = str .. hours .. "小时";
    else
        str = str .. "0" .. hours .. "小时";
    end

    if minutes > 9 then
        str = str .. minutes .. "分";
    else
        str = str .. "0" .. minutes .. "分";
    end
    return str;
end

-- 获取时间字串
function getTimeDayOrHMSStr(timesecond)

    local days = Mathf.Floor(timesecond / (3600 * 24))
    local hours = Mathf.Floor((timesecond % (3600 * 24)) / 3600)
    local minutes = Mathf.Floor((timesecond % (3600 * 24)) % 3600 / 60)
    local seconds = timesecond % 60
    local str = nil
    if days > 0 then
        str = days .. "天"
        return str
    end
    if hours > 9 then
        str = hours
    else
        str = "0" .. hours
    end

    if minutes > 9 then
        str = str .. ":" .. minutes
    else
        str = str .. ":0" .. minutes
    end
    if seconds > 9 then
        str = str .. ":" .. seconds
    else
        str = str .. ":" .. "0" .. seconds
    end
    return str
end

function GetDayByYearAndMonth(year, month)
    if month == 2 then
        if year / 4 == 0 then
            return 29
        else
            return 28
        end
    end

    if table.contains(M31, month) then
        return 31
    else
        return 30
    end
end

function ConvertTimeDataForActivity(data)
    if data.hour < 5 then
        TimeDataMinusOneDay(data)
    end
    return data
end

function TimeDataMinusOneDay(data)
    data.day = data.day - 1
    if data.day == 0 then
        data.month = data.month - 1
        if data.month == 0 then
            data.month = 12
            data.year = data.year - 1
        end
        data.day = GetDayByYearAndMonth(data.year, data.month)
    end
    return data
end

function GetTimeStampDayDis(ts1, ts2)
    if ts1 > ts2 then
        return 0
    end
    local data1 = ConvertTimeDataForActivity(formatUnixTime2Date(math.floor(ts1)))
    local data2 = ConvertTimeDataForActivity(formatUnixTime2Date(math.floor(ts2)))
    local function IsTimeDayEqual(d1, d2)
        if d1.year == d2.year and d1.month == d2.month and d1.day == d2.day then
            return true
        end
        return false
    end
    local day = 0
    while (not IsTimeDayEqual(data1, data2)) do
        day = day + 1
        data2 = TimeDataMinusOneDay(data2)
    end
    return day
end

function ConvertCSharpLongNumber(num)
    return tonumber(tostring(num))
end

function ConvertTimeStamp(timeStamp)
    local data = formatUnixTime2Date(math.floor(timeStamp))
    local convertNum = data.year * 500 + data.month * 50 + data.day
    if data.hour < 5 then
        convertNum = convertNum - 1
    end
    return convertNum
end
function getTimeStrType2(offlineTime)
    local timesecond = (Config.serverTime - offlineTime) / 1000;
    local days = Mathf.Floor(timesecond / (3600 * 24));
    local hours = Mathf.Floor((timesecond % (3600 * 24)) / 3600);
    local minutes = Mathf.Floor((timesecond % (3600 * 24)) % 3600 / 60);
    local str = "";
    if days > 0 then
        if days <= 7 then
            str = string.format(getLanguage("Sys_Friend_Offline2"), days);
            return str;
        else
            str = Util.TimeDateFormat(offlineTime)
            return str;
        end
    end
    if hours > 0 then
        str = string.format(getLanguage("Sys_Friend_Offline1"), hours);
        return str;
    end

    if minutes > 0 then
        str = minutes .. "分钟前";
    else
        str = math.floor(timesecond) .. "秒前";
    end
    return str;
end

function clearFile(fileName,content)
    local f = assert(io.open(fileName,'w'))
    f:write(content)
    f:close()
end

function writeFile(fileName,content)
    local f = assert(io.open(fileName,'a'))
    f:write(content)
    f:close()
end
function sendReqMessage(reqProto, showWaiting, callback)
    --[[if IsWindowEditor() and reqProto.msgId ~= 100201 then
        writeFile(Application.dataPath..'/Editor/sendInfo.txt',"\n"..reqProto.msgId.." " ..os.date("%X",os.time()))
    end--]]
    if lastMessageId == reqProto.msgId and os.time() - lastMessageTime < 1 then
        if table.contains(tableMessageLimit, reqProto.msgId) then
            --UISysTips.AddMsg(getLanguage("Sys_Friend_Tips2"))
            return
        end
    end
    lastMessageTime = os.time()
    lastMessageId = reqProto.msgId
    if Config.DEBUG_DUMP_MSG and reqProto.msgId ~= 100201 then
        dataTable.PrintTableTogether(string.format("send msg, msgid -> %d", reqProto.msgId), reqProto);
    end

    if LocalServer.ContainsReq(reqProto.msgId) then
        LocalServer.Receive(reqProto, callback)
        return
    end
    local buff = WebMsgMgr:createByteBuff();
    buff:WriteInt32(1227)
    buff:WriteInt32(0);
    buff:WriteInt32(0);
    buff:WriteInt32(0);
    buff:WriteInt32(0);
    reqProto:WriteBuff(buff);

    buff:SetInt(4, buff.Length - 8);

    if showWaiting == nil then
        showWaiting = 1;
    end

    if showWaiting == 1 then
        table.insert(NetMsgDecoder.hasSendMsgOrder, WebMsgManager.MsgOrder);
    end

    if callback ~= nil and NetMsgDecoder.callbackFun[WebMsgManager.MsgOrder] == nil then
        NetMsgDecoder.callbackFun[WebMsgManager.MsgOrder] = callback;
    end


    --WebMsgManager.index = WebMsgManager.index + 1;
    --WebMsgManager.msgOrder = WebMsgManager.msgOrder + 1;

    WebMsgMgr:SendByteBuffMsg(reqProto.msgId, buff, logicMgr.LoginManager.GetServerName(), showWaiting, callback);

end

function sendSceneReqMessage(reqProto, showWaiting, callback)
    if Config.DEBUG_DUMP_MSG and reqProto.msgId ~= 100201 then
        dataTable.PrintTableTogether(string.format("send msg, msgid -> %d", reqProto.msgId), reqProto);
    end

    if LocalServer.ContainsReq(reqProto.msgId) then
        LocalServer.Receive(reqProto, callback)
        return
    end
    local buff = WebMsgMgr:createByteBuff();
    buff:WriteInt32(1227)
    buff:WriteInt32(0);
    buff:WriteInt32(0);
    buff:WriteInt32(0);
    buff:WriteInt32(0);
    reqProto:WriteBuff(buff);

    buff:SetInt(4, buff.Length - 8);

    if showWaiting == nil then
        showWaiting = 1;
    end

    if showWaiting == 1 then
        table.insert(NetMsgDecoder.hasSendMsgOrder, WebMsgManager.MsgOrder);
    end

    if callback ~= nil and NetMsgDecoder.callbackFun[WebMsgManager.msgOrder] == nil then
        NetMsgDecoder.callbackFun[WebMsgManager.MsgOrder] = callback;
    end


    --WebMsgManager.index = WebMsgManager.index + 1;
    --WebMsgManager.msgOrder = WebMsgManager.msgOrder + 1;

    WebMsgMgr:SendSceneByteBuffMsg(reqProto.msgId, buff, "", showWaiting, callback);

end

--自定义大小buffer
function sendSmartReqMessage(reqProto, showWaiting, callback, byte_size)
    if Config.DEBUG_DUMP_MSG and reqProto.msgId ~= 100201 then
        dataTable.PrintTableTogether(string.format("send msg, msgid -> %d", reqProto.msgId), reqProto);
    end

    if LocalServer.ContainsReq(reqProto.msgId) then
        LocalServer.Receive(reqProto, callback)
        return
    end
    if byte_size < 1024 or byte_size == nil then
        byte_size = 1024
    end
    local buff = WebMsgMgr:createByteBuffSize(byte_size)
    buff:WriteInt32(1227)
    buff:WriteInt32(0);
    buff:WriteInt32(0);
    buff:WriteInt32(0);
    buff:WriteInt32(0);
    reqProto:WriteBuff(buff);

    buff:SetInt(4, buff.Length - 8);

    if showWaiting == nil then
        showWaiting = 1;
    end

    if showWaiting == 1 then
        table.insert(NetMsgDecoder.hasSendMsgOrder, WebMsgManager.MsgOrder);
    end

    if callback ~= nil and NetMsgDecoder.callbackFun[WebMsgManager.msgOrder] == nil then
        NetMsgDecoder.callbackFun[WebMsgManager.MsgOrder] = callback;
    end
    WebMsgMgr:SendByteBuffMsg(reqProto.msgId, buff, logicMgr.LoginManager.GetServerName(), showWaiting, callback);

end

function gmcommand(command)
    if command == "ssr" then
        local cfg = dataTable.getDataTable("t_hero")
        for k, v in pairs(cfg) do
            if v.f_HeroID < 200 and v.f_HeroInitialStar == 60 then
                command = " @gm add partner " .. v.f_HeroID
                DoGmCommand(command)
            end
        end
        return
    elseif command == "GoldenFinger" then
        UIBattleMain.openGoldenFinger = true
    elseif string.Contains(command, "story") then
        local temp = string.split(command, " ")
        if #temp == 2 then
            logicMgr.StoryMgr.ShowStoryById(temp[2])
        end
        return
    elseif string.Contains(command, "guide") then
        local temp = string.split(command, " ")
        if #temp == 2 then
            Util.SetPlayerPrefs(HeroManager.GetRoleId() .. "gmguideclose", temp[2])
            logicMgr.GuideMgr.LoadLocal()
        elseif string.Contains(temp[2], "debug") then
            logicMgr.GuideMgr.LoadDebug(tonumber(temp[3]))
        end
        return
    elseif string.Contains(command, "god bless") then
        DoGmCommand("@gm add item 1 100000000") --金币
        DoGmCommand("@gm add item 3 10000000") --金币
        DoGmCommand("@gm add item 6 10000000") --英雄经验
        DoGmCommand("@gm add item 18 10000000") --英雄升级材料
        DoGmCommand("@gm add item 16 999") --大秘境挑战券
        DoGmCommand("@gm add item 13 99999") --专武材料
        return
    elseif string.Contains(command, "神装") then
        local temp = string.split(command, " ")
        if #temp == 2 then
            local type = tonumber(temp[2])
            local cfg = dataTable.getDataTable("t_equip")
            for k, v in pairs(cfg) do
                if v.f_EquipQuality == 15 and v.f_EquipHeroType == type then
                    DoGmCommand("@gm add item " .. v.f_EquipId .. " 1")
                end
            end
        end
        return
    elseif string.Contains(command, "lbs ") then
        local temp = string.split(command, " ")
        if #temp >= 2 then
            local flag = temp[2] --true false
            local name = temp[3]
            if flag == "true" and not string.IsNilOrEmpty(name) then
                SetPlayerPrefs("DebugLbsLocation", name)
            elseif flag == "false" then
                SetPlayerPrefs("DebugLbsLocation", "")
            end
        end
        return
    elseif string.Contains(command, "lua ") then
        local str = string.sub(command, 4)
        local f = loadstring(str)
        if f then
            f()
        end
        return
    elseif string.Contains(command, "chapter end ") then
        local temp = string.split(command, " ")
        if #temp >= 3 then
            local chapter = tonumber(temp[3])
            local cfg = dataTable.getDataTable("t_stage")
            for k, v in pairs(cfg) do
                if v.f_ChapterId == chapter then
                    local next = dataTable.getDataTableByStringId("t_stage", v.f_NextStage, "f_ChapterId")
                    if next ~= chapter then
                        logicMgr.MapManager.setMapId(v.f_StageId)
                        DoGmCommand("@gm skip dungeon " .. v.f_StageId)
                        return
                    end
                end
            end
        end
    elseif string.Contains(command, "@gm skip dungeon") then
        local temp = string.split(command, " ")
        if #temp >= 4 then
            logicMgr.MapManager.setMapId(tonumber(temp[4]) or logicMgr.MapManager.getMapId())
        end
    elseif command == "gc" then
        DumpLuaMemSample()
        return
    end
    DoGmCommand(command)
end
function DoGmCommand(command)
    local req = ReqGmCommandMessage.New();
    req.command = command;
    sendReqMessage(req, 0);
end
function concactTable(srcTbl, mergeTbl)

    if mergeTbl == nil then
        return ;
    end
    for i = 1, #mergeTbl do
        table.insert(srcTbl, mergeTbl[i]);
    end

end
function IsWindowEditor()
    return UnityEngine.Application.platform == UnityEngine.RuntimePlatform.WindowsEditor or UnityEngine.Application.platform == UnityEngine.RuntimePlatform.OSXEditor
end
function showFightDamage(damgesResult, target, self)

    --local prefabName = "";
    local showTxt = "";
    local hurttype = damgesResult.type;
    local tag = damgesResult.buffTag;
    local addState = damgesResult.addState;
    local buffShow = damgesResult.buffShow

    if hurttype then
        if false and (self == nil or self.teamIndex == 1) then
            if hurttype ~= Config.damageType.heal then
                --只有治疗弹
                return
            end
        end
        if hurttype == Config.damageType.heal then
            if damgesResult.damage <= 10 then
                return
            end
        end
    end
    if tag and #tag > 0 then
        if tag[1] == Config.BuffTagType.Tyrants then
            --只有霸体不弹
            return
        elseif tag[1] == Config.BuffTagType.Nihility then
            --虚无状态
            self.skeletonAnimation.Skeleton.A = 0.5;
            return
        elseif tag[1] == Config.BuffTagType.HITDOWN then
            --击倒
            self:setHitShowType(EnumConst.HitShowType.HITDOWN);
            self:setAniState(Config.AnimationState.HITDOWN, nil, false, true);
            return
        end
    end
    if tag and #tag == 0 then
        return
    end
    local backObj = UIBloodBar.GetTextMesh();
    local controller = backObj:GetController("state");
    local textMesh = backObj:GetChild("text");
    local fontController = textMesh:GetController("font");
    local trans = nil;

    trans = backObj:GetTransition("State");

    --hurtType是伤害是跳的
    if hurttype then
        if hurttype == Config.damageType.normal then
            controller.selectedIndex = 0
            fontController.selectedIndex = 3
            trans = backObj:GetTransition("Normal")
        elseif hurttype == Config.damageType.crit then
            controller.selectedIndex = 2
            fontController.selectedIndex = 2
            trans = backObj:GetTransition("Crit")
        elseif hurttype == Config.damageType.dodge then
            controller.selectedIndex = 3
            trans = backObj:GetTransition("State")
        elseif hurttype == Config.damageType.immunity then
            controller.selectedIndex = 8
            trans = backObj:GetTransition("State")
        elseif hurttype == Config.damageType.heal then
            controller.selectedIndex = 0
            fontController.selectedIndex = 1
            trans = backObj:GetTransition("Heal")
        elseif hurttype == Config.damageType.sp then
            if damgesResult.damage < 0 then
                controller.selectedIndex = 4
            else
                controller.selectedIndex = 9
            end
            fontController.selectedIndex = 4
            trans = backObj:GetTransition("Sp")
        elseif hurttype == Config.damageType.shields then
            controller.selectedIndex = 5
            trans = backObj:GetTransition("State")
        else
            controller.selectedIndex = 0
            trans = backObj:GetTransition("Normal")
        end
    end

    --tag是加buff时触发的
    if tag and #tag > 0 then
        if tag[1] == Config.BuffTagType.Freeze then
            controller.selectedIndex = 7;
        elseif tag[1] == Config.BuffTagType.Vertigo then
            controller.selectedIndex = 10;
        elseif tag[1] == Config.BuffTagType.PhysicDefUp then
            controller.selectedIndex = 11;
        elseif tag[1] == Config.BuffTagType.PhysicDefDown then
            controller.selectedIndex = 12;
        elseif tag[1] == Config.BuffTagType.MagicDefUp then
            controller.selectedIndex = 14;
        elseif tag[1] == Config.BuffTagType.MagicDefDown then
            controller.selectedIndex = 15;
        elseif tag[1] == Config.BuffTagType.Petrifaction then
            controller.selectedIndex = 16;
        elseif tag[1] == Config.BuffTagType.cham then
            controller.selectedIndex = 17;
        elseif tag[1] == Config.BuffTagType.Silent then
            controller.selectedIndex = 18;
        elseif tag[1] == Config.BuffTagType.Blinding then
            controller.selectedIndex = 19;
        elseif tag[1] == Config.BuffTagType.AttackAdd then
            controller.selectedIndex = 20;
        elseif tag[1] == Config.BuffTagType.AttackDown then
            controller.selectedIndex = 21;
        else
            print("this Tag has not jump num Type, tag==" .. tag[1]);
            UIBloodBar.RemoveBloodFly(backObj);
            return ;
        end
        --printError("tag=="..tag[1])
    end

    if addState then
        if addState == EnumConst.AdditionalStateType.BLOCK then
            controller.selectedIndex = 1;

        elseif addState == EnumConst.AdditionalStateType.SUPER_ARMOR then
            controller.selectedIndex = 22;
        else
            print("this addState has not jump num Type, tag==" .. addState);
            UIBloodBar.RemoveBloodFly(backObj);
            return ;
        end
        --printError("addState=="..addState)
    end
    --local objectPool = objectPoolMgr;
    --local poolName = string.gsub(prefabName, "Prefabs/Battle/", "");
    --objectPool:Get(poolName);
    if (backObj == nil) then
        return
    end
    local camera = FightManager.getCamera();
    if target ~= nil and camera ~= nil then
        local pos = Util.WorldToScreenPoint(camera, target.transform.position);
        local tpos = Vector2.New()
        tpos:Set(pos.x + Screen.width * 0.5, Screen.height * 0.5 - pos.y)
        local pt = FairyGUI.GRoot.inst:GlobalToLocal(tpos);
        distanceY = distanceY * -1;
        local offsetY = 180;
        if self.isBoss then
            offsetY = offsetY * FightManager.bossScale;
        end
        backObj:SetXY(pt.x, pt.y - offsetY + distanceY)
        self.backObj = backObj
    end
    if damgesResult.damage then
        local showText = textMesh:GetChild("n" .. fontController.selectedIndex)
        showText.text = jumpWord(damgesResult.damage, hurttype);
    end
    --printError("伤害跳字，id, damage", self.fighterId, damgesResult.damage)
    trans:Play(function()
        UIBloodBar.RemoveBloodFly(backObj);
    end)
end

--
function loadResource2(name, type, callback)
    if type == "Model" then
        resMgr:LoadModelPrefab(name, callback);
    elseif type == "Effect" then
        resMgr:LoadModelPrefab(name, callback);
    elseif type == "Map" then
        resMgr:LoadMapPrefab(name, callback);
    elseif type == "UI" then
        local resName, packageName = getUIName(name)
        createPanel(packageName, resName, callback)
        --resMgr:LoadUIPrefab(name, callback);
    elseif type == "FlyText" then
        resMgr:LoadPrefab(name, callback);
    end
end
function loadResource(name, callback)
    local array = string.split(name, "#");
    if #array > 1 then
        loadResource2(array[1], array[2], callback)
    end
end

local spineDict   = {}
local wrapperDict = {}
--- 创建spine动画
---@param tag      string   功能标识
---@param resName  string   动画资源
---@param loader   GObject  动画加载器
---@param complete function 创建完成回调
function createSpineEff(tag, resName, loader, complete)
    if string.IsNilOrEmpty(tag) or string.IsNilOrEmpty(resName) or loader == nil then
        return
    end

    if spineDict[tag] == nil then
        spineDict[tag] = {}
    end
    if wrapperDict[tag] == nil then
        wrapperDict[tag] = {}
    end
    if wrapperDict[tag][resName] == nil then
        wrapperDict[tag][resName] = GoWrapper.New()
    end

    resMgr:LoadModelPrefab(resName, function(prefab)
        local wrapper = wrapperDict[tag][resName]
        if wrapper == nil then
            return
        end
        if spineDict[tag][resName] ~= nil then
            destroyImmediate(spineDict[tag][resName])
            spineDict[tag][resName] = nil
        end

        local spineObj = newObject(prefab)
        wrapper:SetWrapTarget(spineObj, false)
        Util.Lua_SetLocalRotationXYZ(spineObj.transform, 0, 0, 0)
        loader:SetNativeObject(wrapper)
        spineDict[tag][resName] = spineObj

        if complete then complete() end
    end)
end
--- 根据tag销毁spine动画
---@param tag string 功能标识
function destroySpineEffByTag(tag)
    if string.IsNilOrEmpty(tag) then
        return
    end

    if spineDict[tag] ~= nil then
        local dict = spineDict[tag]
        for _, spineObj in pairs(dict) do
            destroyImmediate(spineObj)
            spineObj = nil
        end
        spineDict[tag] = nil
    end
    if wrapperDict[tag] ~= nil then
        local dict = wrapperDict[tag]
        for _, wrapper in pairs(dict) do
            destroyGoWraper(wrapper)
            wrapper = nil
        end
        wrapperDict[tag] = nil
    end
end
--- 销毁spine动画
function destroyAllSpineEff()
    for _, dict in pairs(spineDict) do
        for _, spineObj in pairs(dict) do
            destroyImmediate(spineObj)
            spineObj = nil
        end
        dict = nil
    end
    spineDict = {}

    for _, dict in pairs(wrapperDict) do
        for _, wrapper in pairs(dict) do
            destroyGoWraper(wrapper)
            wrapper = nil
        end
        dict = nil
    end
    wrapperDict = {}
end

function fightLog(str)
    if FightManager.fight ~= nil and false then
        Util.Log("[" .. FightManager.fight.frame .. "]" .. str);
        FightManager.printMsg = FightManager.printMsg .. "[" .. FightManager.fight.frame .. "]" .. str .. "\n";
    end
end

function fightShowLog(str)
    if false then
        Util.Log(str);
        FightManager.printMsg = FightManager.printMsg .. str .. "\n";
    end
end

function overlap(pos1, rec1, pos2, rec2)
    if (Mathf.Floor(rec1.x * EnumConst.TEN_THOUSANDTH + rec2.x * EnumConst.TEN_THOUSANDTH) >= Mathf.Floor(Mathf.Abs(pos1.x * EnumConst.TEN_THOUSANDTH - pos2.x * EnumConst.TEN_THOUSANDTH) * 2)
            and Mathf.Floor(rec1.y * EnumConst.TEN_THOUSANDTH + rec2.y * EnumConst.TEN_THOUSANDTH) >= Mathf.Floor(math.abs(pos1.y * EnumConst.TEN_THOUSANDTH - pos2.y * EnumConst.TEN_THOUSANDTH) * 2)) then
        return true;
    else
        return false;
    end
end

--[[**
* 计算跑向位置
*
* @param fighter
* @param rec1
* @param target
* @return
*/
--]]
function runToPosition(fighter, rec1, target)
    local MIN_ATTACK_ROUND = tonumber(G.dataTable["t_global"]["2"].f_string_value);
    local x = 0;
    local leftOrRight = -1;
    if fighter.position.x < target.position.x then
        leftOrRight = -1;
    else
        leftOrRight = 1;
    end
    -- 横向在攻击距离之外， 需要横向跑动
    if math.abs(fighter.position.x - target.position.x) * 2 > rec1.x + target.shape.x - 0.1 then
        x = target.position.x + leftOrRight * (rec1.x + target.shape.x - 0.1) / 2;
    elseif math.abs(fighter.position.x - target.position.x) < MIN_ATTACK_ROUND then
        x = target.position.x + leftOrRight * MIN_ATTACK_ROUND;
        if x < EnumConst.FIGHT_INNER_ROUND[1][1] or x > EnumConst.FIGHT_INNER_ROUND[2][1] then
            leftOrRight = -leftOrRight;
            x = target.position.x + leftOrRight * MIN_ATTACK_ROUND;
        end
    else
        x = fighter.position.x;
    end

    local y = 0;
    local upOrDown = -1;
    if fighter.position.y < target.position.y then
        upOrDown = -1;
    else
        upOrDown = 1;
    end
    -- 横向在攻击距离之外， 需要横向跑动
    if math.abs(fighter.position.y - target.position.y) * 2 > rec1.y + target.shape.y - 0.1 then
        y = target.position.y + upOrDown * (rec1.y + target.shape.y - 0.1) / 2;
    else
        y = fighter.position.y;
    end
    return Vector2(x, y);
end

function printVector2(x, y)
    return string.format("%f", x) .. "," .. string.format("%f", y)
end

function checkOut(fighter, round)
    return checkOut1(fighter.position, fighter.shape, round, false, false);
end

function checkOut1(point, shape, round, ignoreLeft, ignoreRight)
    local out = false;
    if not ignoreRight and point.x + shape.x / 2 > round[2][1] then
        local x = round[2][1] - shape.x / 2;
        point.x = x;
        out = true;
    elseif not ignoreLeft and point.x - shape.x / 2 < round[1][1] then
        local x = round[1][1] + shape.x / 2;
        point.x = x;
        out = true;
    end
    if point.z + shape.y / 2 > round[1][3] then
        local y = round[1][3] - shape.y / 2;
        point.z = y;
        out = true;
    elseif point.z - shape.y / 2 < round[2][3] then
        local y = round[2][3] + shape.y / 2;
        point.z = y;
        out = true;
    end
    return out;
end

function jumpWord(number, damageType)
    local num = tostring(math.abs(number));
    local str = "";
    if damageType == Config.damageType.normal then
        if number >= 1000 then
            local len = string.len(num);
            for i = 1, len do
                str = str .. string.sub(num, i, i)
                if (len - i) ~= 0 and (len - i) % 3 == 0 then
                    str = str .. ",";
                end
            end
        else
            str = str .. num;
        end
    elseif damageType == Config.damageType.crit then
        if number >= 1000 then
            local len = string.len(num);
            for i = 1, len do
                str = str .. string.sub(num, i, i);
                if (len - i) ~= 0 and (len - i) % 3 == 0 then
                    str = str .. ",";
                end

            end
        else
            str = str .. num;
        end
    elseif damageType == Config.damageType.heal then
        str = str .. "+";
        if number >= 1000 then
            local len = string.len(num);
            for i = 1, len do
                str = str .. string.sub(num, i, i);
                if (len - i) ~= 0 and (len - i) % 3 == 0 then
                    str = str .. ",";
                end
            end
        else
            str = str .. num;
        end
    elseif damageType == Config.damageType.shields then
        --str = string.format("<sprite=%s>", Config.damageMark.SHIELDS);
    elseif damageType == Config.damageType.sp then

        number = math.abs(number);
        str = str .. num
    end
    return str;
end

function toNumber(number)
    if number == nil or number == "" then
        return 0;
    else
        return tonumber(number);
    end
end

--- int64，long类型数字转number
--- @param longVal number C#传过来的long类型
--- @return number
function int64ToNumber(longVal)
    return tonumber(tostring(longVal));
end

function playUISpineAnimation(effResource)
    if effResource ~= nil then
        if effResource.gameObject.layer ~= LayerMask.NameToLayer("UI") then
            setLayer(effResource.gameObject, LayerMask.NameToLayer("UI"));
        end

        local skeGraphic = effResource:GetComponent("SkeletonGraphic");
        if skeGraphic ~= nil and skeGraphic.Skeleton and skeGraphic.AnimationState then
            skeGraphic.Skeleton:SetSkin(skeGraphic.initialSkinName);
            skeGraphic.AnimationState:SetAnimation(0, skeGraphic.startingAnimation, false);
            skeGraphic:Update(0);
        end
    end
end

function getRoleFlip(role, target)
    --如果没有需要改变， keep原来的方向
    local IsNull = HelperFunc.IsNull
    local flip = not IsNull(role) and role.flip or false;
    if not IsNull(role) and not IsNull(target) and role ~= target and not IsNull(target.roleObj) and not IsNull(role.roleObj) then
        local tPosx = Util.Lua_GetLocalPositionXYZ(target.roleObj, 0, 0, 0)
        local rPosx = Util.Lua_GetLocalPositionXYZ(role.roleObj, 0, 0, 0)
        local offsetx = tPosx - rPosx
        if offsetx > 0.05 then
            flip = false;
        elseif offsetx < -0.05 then
            flip = true;
        end

        --logMy('getRoleFlip', offset.x, role.fighterId, target.fighterId)
    end
    return flip, xDet;
end

function setGray(go, isSet)
    local img = go.transform:GetComponent("Image");
    if img ~= nil then
        img.material = isSet and HelperFunc.GetGrayMaterial() or nil;
    end
end
function string.Contains(s, subStr)
    return string.find(s, subStr) ~= nil
end
function string.startsWith(s, prefix)
    return string.find(s, prefix) == 1
end
function string.IsNilOrEmpty(s)
    return s == "" or s == nil
end
--名字短点
function string.IsEmpty(s)
    return s == "" or s == nil
end
function table.clear(tb)
    if not tb then
        return
    end
    for k, v in pairs(tb) do
        tb[k] = nil
    end
end

function table.nums(t)
    local count = 0
    for k, v in pairs(t) do
        count = count + 1
    end
    return count
end
function table.isEmpty(t)
    for k, v in pairs(t) do
        return false
    end
    return true
end
function table.addRange(table, table2)
    if not table or not table2 then
        return
    end
    for k, v in pairs(table2) do
        table[k] = v
    end
end
function table.addAllToArr(table1, table2)
    if not table1 or not table2 then
        return
    end
    for k, v in pairs(table2) do
        table.insert(table1, v)
    end
end
function table.reverse(tab)
    local tmp = {}
    for i = 1, #tab do
        --local key = #tab
        tmp[i] = table.remove(tab)
    end

    return tmp
end

-- 在集合A中不在集合B中
function table.InANoB(A, B)
    if A == nil then
        return nil
    end

    if B == nil then
        return A
    end

    local it = {}
    for key1, value1 in pairs(A) do
        local isIn = false
        for key2, value2 in pairs(B) do
            if value1 == value2 then
                isIn = true
                break
            end
        end
        if not isIn then
            table.insert(it, value1)
        end
    end
    return it
end
---@return number,number,number x,y,z
function GetMousePosition()
    return Util.GetMousePosition(0, 0, 0)
end
---@return number,number  x,y
function FGUIScreenToLocal(go, screenX, screenY)
    return Util.FGUIScreenToLocal(go, screenX, screenY, 0, 0)
end
function GetDayOfYear(time)
    return tonumber(os.date("%j", time))
end
--根据playerId存
function SetPlayerPrefs(key, value)
    if HeroManager then
        Util.SetPlayerPrefs(HeroManager.GetRoleId() .. AppConst.ServerId .. tostring(key), tostring(value))
    end
end
function GetPlayerPrefs(key)
    if HeroManager then
        return Util.GetPlayerPrefs(HeroManager.GetRoleId() .. AppConst.ServerId .. tostring(key))
    else
        return nil
    end
end
--跨天标记，true：当天，false:跨天
function GetPlayerPrefsTodayFlag(key, serverTime)
    local value = GetPlayerPrefs(key)
    if not serverTime then
        serverTime = HelperFunc.GetServerTime()
    end
    local month = tonumber(os.date("%m", serverTime))
    local day = tonumber(os.date("%d", serverTime))
    local v = month .. day
    if value == v then
        return true
    else
        return false
    end
end
--跨天标记
function SetPlayerPrefsTodayFlag(key, hasValue, serverTime)
    if not serverTime then
        serverTime = HelperFunc.GetServerTime()
    end
    local month = tonumber(os.date("%m", serverTime))
    local day = tonumber(os.date("%d", serverTime))
    local v = month .. day
    if hasValue then
        SetPlayerPrefs(key, v)
    else
        SetPlayerPrefs(key, "")
    end

end
--ui界面加速
function UISpeed()
    local ts = tonumber(GetPlayerPrefs("AddUISpeed") or 1)
    if ts > 1 then
        Time.timeScale = ts
    end
end
--5点刷新标志 true:超过5点，false:没到5点
function GetPlayerPrefsAfter5AMFlag(key)
    local serverTime = HelperFunc.GetServerTime()
    serverTime = serverTime - 5 * 3600
    local flag = GetPlayerPrefsTodayFlag(key, serverTime)
    return not flag
end
--5点刷新标志
function SetPlayerPrefsAfter5AMFlag(key)
    --local nextTime = logicMgr.CronMgr.GetNextDateTime("0 0 5 * * ? *",serverTime)
    local serverTime = HelperFunc.GetServerTime()
    serverTime = serverTime - 5 * 3600
    SetPlayerPrefsTodayFlag(key, true, serverTime)
end
------------------- 对Vector的优化 缓存写法 原实现每次调用都会申请内存----------------------
function Vector3.NormalizeEX(v)
    local x, y, z = v.x, v.y, v.z
    local num = math.sqrt(x * x + y * y + z * z)
    if num > 1e-5 then
        v.x, v.y, v.z = x / num, y / num, z / num
        return v
    end
    v.x, v.y, v.z = 0, 0, 0
    return v
end

function Vector3.AddEX(va, vb)
    va.x, va.y, va.z = va.x + vb.x, va.y + vb.y, va.z + vb.z
    return va
end

function Vector3.SubEX(va, vb)
    va.x, va.y, va.z = va.x - vb.x, va.y - vb.y, va.z - vb.z
    return va
end

function Vector3.DivEX(va, d)
    va.x, va.y, va.z = va.x / d, va.y / d, va.z / d
    return va
end

function Vector3.MulEX(va, d)
    if type(d) == "number" then
        va.x, va.y, va.z = va.x * d, va.y * d, va.z * d
        return va
    else
        local vec = va:Clone()
        vec:MulQuat(d)
        return vec
    end
end
function CreateBGUIPanel(package, component)

    local cameraTran = panelMgr.BGFGUICameraMain
    if not cameraTran then
        log("找不到背景相机")
        return
    end
    cameraTran.gameObject:SetActive(true)
    AddUIPackage(package)
    local panelTran = cameraTran:Find("UIPanel")
    ---@type FairyGUI.UIPanel
    local panel = panelTran:GetComponent(TUIPanel)
    panel.packageName = package
    panel.componentName = component
    panel:CreateUI()
    if panel.ui == nil then
        return nil
    end
    --local w = panel.ui.width
    --local h = panel.ui.height
    --panel.ui:SetXY(-w / 2, -h / 2)
    return panel.ui
end
function CloseBGUIPanel()
    local cameraTran = panelMgr.BGFGUICameraMain
    if not cameraTran then
        log("找不到背景相机")
        return
    end
    local panelTran = cameraTran:Find("UIPanel")
    ---@type FairyGUI.UIPanel
    local panel = panelTran:GetComponent(TUIPanel)
    panel.packageName = ""
    panel.componentName = ""
    panel:CreateUI()
    cameraTran.gameObject:SetActive(false)
end
function CheckHeroSkillUpgrade(partnerID, customLevel)
    local hero = logicMgr.HeroManager.getHeroDatabyPartnerId(tostring(partnerID), true)
    --local skillInfos= logicMgr.SkillManager.getSkillInfosById(partnerID);
    local level = customLevel
    if customLevel == nil then
        level = logicMgr.HeroManager.getHeroShowLevelByPartnerId(partnerID)
    end
    local skillInfos = logicMgr.SkillManager.getSkillInfosById(hero.partnerModelId)
    local flag = -1   -- -1:没有解锁或升级 0：解锁 1：升级
    local type
    for i, v in ipairs(skillInfos) do
        local data = G.dataTable["t_skillunlocker"][tostring(v.f_SkillID)]
        local unlock = data.f_SkillUnlocker
        if unlock ~= 1 and level == unlock - 1 then
            flag = 0
            type = data.f_SkillType
            break
        end
    end
    if flag == 0 then
        return flag, type
    end
    for i, v in ipairs(skillInfos) do
        local starArray = (v.f_SkillList)
        local willBreak = false
        for i = 1, #starArray do
            if G.dataTable["t_skillunlocker"][tostring(starArray[i])] then
                local limitlv = G.dataTable["t_skillunlocker"][tostring(starArray[i])].f_SkillUnlocker
                if level == limitlv - 1 then
                    flag = 1
                    type = G.dataTable["t_skillunlocker"][tostring(starArray[i])].f_SkillType
                    willBreak = true
                    break
                end
            end
        end
        if willBreak then
            break
        end
    end
    return flag, type
end
function SetBtnSkill(btnSkill, hero, skillType, nameText, isNext)
    if btnSkill == nil then
        return
    end
    local skillInfo = logicMgr.SkillManager.getSkillInfosById(hero.partnerModelId)[skillType]
    local level = logicMgr.HeroManager.getHeroShowLevelByPartnerId(hero.partnerId)
    local curLv = logicMgr.SkillManager.getSkillLv(skillInfo, level)
    local icon = btnSkill:GetChild("Mask"):GetChild("Icon")
    local objLv = btnSkill:GetController("CtrlLv")
    local txtLv = btnSkill:GetChild("SkillLevelText")
    local btnSwitch = btnSkill:GetChild("SwitchBtn")
    objLv:SetSelectedIndex(1)
    if (isNext) then
        curLv = curLv + 1
    end
    if (nameText ~= nil) then
        nameText.text = getLanguage(skillInfo.f_SkillName)
    end
    txtLv.text = tostring(curLv);
    btnSwitch.visible = false
    local iconName = tostring(skillInfo["f_SkillIcon"]);
    icon.url = "ui://SkillAtlas/" .. iconName

end

LEV_UP_LIMIT = {
    OK = 0;
    MAX_LEV = 1, --达到最大等级
    QUALITY_LIMIT = 2, --达到当前品质（星级）的最大等级
    LINK_LIMIT = 3, --智慧链限制
    EXP_LIMIT = 4, --英雄经验限制
    ITEM_LIMIT = 5, --资源限制（道具消耗）
}
--- 检测是否满足升级条件
function CheckHeroCanLevUp(heroData, showTips, items)
    --达到最大等级
    if heroData.level >= logicMgr.HeroManager.GetLimitLevel() then
        if showTips then
            UISysTips.AddMsg(getLanguage("Sys_MsgMaxLevel"))
        end
        return LEV_UP_LIMIT.MAX_LEV
    end
    --品质（星级）限制
    local cfgID = toNumber(heroData["partnerModelId"]) * 1000 + heroData["star"]
    local cfgInfo = dataTable.getRow("t_herorank", cfgID)
    if heroData.level >= cfgInfo["f_LevelCap"] then
        local limitLev = cfgInfo["f_HeroStar"]
        for i = heroData["star"], 150, 10 do
            cfgID = toNumber(heroData["partnerModelId"]) * 1000 + i;
            cfgInfo = dataTable.getRow("t_herorank", cfgID)
            if cfgInfo ~= nil then
                if cfgInfo.f_LevelCap > heroData.level then
                    limitLev = cfgInfo.f_HeroStar;
                    break ;
                end
            end
        end
        local content = string.format(getLanguage("Character_Tips5"), EnumConst.StarType[limitLev / 10])
        if showTips then
            UISysTips.AddMsg(content)
        end
        return LEV_UP_LIMIT.QUALITY_LIMIT, content
    end
    --智慧链限制
    local levCfg = dataTable.getRow("t_characterexp", MakeTopLevelId(heroData.level))
    if levCfg and levCfg["f_LevelupRequirement"] and levCfg["f_LevelupRequirement"] > 0 then
        local limitLev = levCfg["f_LevelupRequirement"]
        if logicMgr.HeroLinkManager.GetHeroLevel() < limitLev then
            local content = string.format(getLanguage("Character_Tips6"), limitLev)
            if showTips then
                UISysTips.AddMsg(content)
            end
            return LEV_UP_LIMIT.LINK_LIMIT, content
        end
    end
    --英雄经验限制
    if charactorExpConfig == nil then
        charactorExpConfig = G.dataTable["t_characterexp"];
    end
    local data = charactorExpConfig[MakeTopLevelId(heroData.level)];
    if data == nil then
        logError(string.format("未找到id为%d的配置", heroData.level))
        return LEV_UP_LIMIT.EXP_LIMIT;
    end
    local costExp = data["f_HeroExp"];
    local hasExp = logicMgr.ItemManager.getCurrencyCountByModelId(CurrencyEnum.HeroExp)
    if items ~= nil then
        if items[CurrencyEnum.HeroExp] == nil then
            items[CurrencyEnum.HeroExp] = hasExp
        else
            hasExp = items[CurrencyEnum.HeroExp]
        end
    end
    if costExp > hasExp then
        if showTips then
            UISysTips.AddMsg(getLanguage("Character_Tips2"));
        end
        return LEV_UP_LIMIT.EXP_LIMIT
    else
        if items ~= nil then
            items[CurrencyEnum.HeroExp] = items[CurrencyEnum.HeroExp] - costExp;
        end
    end

    --资源限制（道具消耗）
    local costs = decodeJsonStrToTabel(data["f_HeroCost"]);
    for i = 1, #costs do
        local item = costs[i];
        local costId = item[1];
        local costNum = item[2];
        local itemType = logicMgr.ItemManager.getItemTabType(costId);
        local hasNum = 0;
        if itemType == EnumConst.ItemTableEnum.Currency then
            hasNum = logicMgr.ItemManager.getCurrencyCountByModelId(costId);
        else
            hasNum = logicMgr.ItemManager.getItemCountbyModelId(tostring(costId));
        end
        if items ~= nil then
            if items[costId] == nil then
                items[costId] = hasNum;
            else
                hasNum = items[costId];
            end
        end
        if costNum > hasNum then
            if showTips then
                UISysTips.AddMsg(getLanguage("Character_Tips2"))
            end
            return LEV_UP_LIMIT.ITEM_LIMIT
        else
            if items ~= nil then
                items[costId] = items[costId] - costNum;
            end
        end
    end
    return LEV_UP_LIMIT.OK
end
---返回过程中所有GTweener
---turns 过程中每轮的max
function PlaySliderAnim(slider, value, turns, Tweeners, endCB, tweenCB)
    local shortTime = 0.5
    local longTime = 2
    local turnNum = 0
    if turns ~= nil then
        turnNum = #turns
    end
    if turnNum < 1 then
        local t1 = slider:TweenValue(value, shortTime):OnComplete(function()
            if endCB then
                endCB()
                KillAllTweeners(Tweeners)
            end
        end)
        table.insert(Tweeners, t1)
    elseif turnNum == 1 then
        local t1 = slider:TweenValue(slider.max, shortTime)
        table.insert(Tweeners, t1)
        t1:OnComplete(function()
            slider.value = 0
            slider.max = turns[1]
            local t2 = slider:TweenValue(value, shortTime)
            if tweenCB then
                tweenCB()
            end
            t2:OnComplete(function()
                if endCB then
                    endCB()
                end
            end)
            table.insert(Tweeners, t2)
        end)
    else
        local time = longTime / (turnNum + 1)
        local start = 1
        local function AddTween()
            if start <= turnNum then
                slider.value = 0
                slider.max = turns[start]
                local t
                if start == turnNum then
                    t = slider:TweenValue(value, time)
                    t:OnComplete(function()
                        if endCB then
                            endCB()
                        end
                    end)
                else
                    t = slider:TweenValue(slider.max, time)
                    t:OnComplete(function()
                        AddTween()
                        if tweenCB then
                            tweenCB()
                        end
                    end)
                end
                table.insert(Tweeners, t)
                start = start + 1
            end
        end
        local tween = slider:TweenValue(slider.max, time)
        table.insert(Tweeners, tween)
        tween:OnComplete(function()
            AddTween()
        end)
    end
    return Tweeners
end
function KillAllTweeners(Tweeners)
    if Tweeners ~= nil then
        for i, v in pairs(Tweeners) do
            if v ~= nil and v.Kill then
                v:Kill()
                v = nil
            end
        end
    end
    Tweeners = {}

end

--[[
    @desc: 敏感词汇过滤
]]
function BlockWordFilter(text, replaceStr)
    replaceStr = replaceStr or "**"

    --local fliterBefore = text
    local b,temp = logicMgr.ChatManager.HasFilterBlockWord(text)
    if b then
        return temp
    else
        return text
    end
end

--[[
    @desc: lua匹配UTF-8汉字
]]
function filter_spec_chars(s)
    local ss = {}
    local k = 1
    while true do
        if k > #s then
            break
        end
        local c = string.byte(s, k)
        if not c then
            break
        end
        if c < 192 then
            if (c >= 48 and c <= 57) or (c >= 65 and c <= 90) or (c >= 97 and c <= 122) then
                table.insert(ss, string.char(c))
            end
            k = k + 1
        elseif c < 224 then
            k = k + 2
        elseif c < 240 then
            if c >= 228 and c <= 233 then
                local c1 = string.byte(s, k + 1)
                local c2 = string.byte(s, k + 2)
                if c1 and c2 then
                    local a1, a2, a3, a4 = 128, 191, 128, 191
                    if c == 228 then
                        a1 = 184
                    elseif c == 233 then
                        a2, a4 = 190, c1 ~= 190 and 191 or 165
                    end
                    if c1 >= a1 and c1 <= a2 and c2 >= a3 and c2 <= a4 then
                        table.insert(ss, string.char(c, c1, c2))
                    end
                end
            end
            k = k + 3
        elseif c < 248 then
            k = k + 4
        elseif c < 252 then
            k = k + 5
        elseif c < 254 then
            k = k + 6
        end
    end
    return table.concat(ss)
end

--需要保证idName的键值为从1开始的连续值
function SortTableData(t, idName, filterFunc)
    if t == nil then
        return nil
    end
    local data = {}
    for i, v in pairs(t) do
        if v[idName] == nil then
            printError("列表排序项名称错误")
            return nil
        end
        if filterFunc ~= nil then
            if filterFunc(v) then
                table.insert(data, v)
            end
        else
            table.insert(data, v)
        end
    end
    table.sort(data, function(a, b)
        return a[idName] < b[idName]
    end)
    return data
end

function getJobIcon(job)
    local icons = { 3, 5, 6, 2, 4, 1 }
    return "ui://ICONAtlas/" .. "New_occupation_" .. icons[job]
end

function addFuncCd(func, timeTable, cd, tip)
    local time = HelperFunc.GetServerTime(false)
    if time - timeTable.value > cd then
        func()
        timeTable.value = time
    else
        UISysTips.AddMsg(tip)
    end
end

function MyLog(s)
    --log("ryh" .. tostring(s))
end
local sample_stack = 0
local cur_sample = {}
local lua_mem_sample = {}
function BeginLuaSample(name, gc)
    if gc then
        collectgarbage("collect")
        collectgarbage("collect")
        collectgarbage("collect")
        collectgarbage("collect")
    end
    sample_stack = sample_stack + 1
    local tb = TablePool.Get()
    tb.name = name
    tb.men = collectgarbage("count")
    cur_sample[sample_stack] = tb
end
function EndLuaSample(name, gc)
    local stack = cur_sample[sample_stack]
    if stack.name ~= name then
        logError("BeginLuaSample 必须和 EndLuaSample 成对出现 name:" .. name)
        return
    end
    cur_sample[sample_stack] = nil
    sample_stack = sample_stack - 1
    if gc then
        collectgarbage("collect")
        collectgarbage("collect")
        collectgarbage("collect")
        collectgarbage("collect")
    end
    local cur_men = collectgarbage("count")
    local add = cur_men - stack.men
    local data = lua_mem_sample[name]
    if data and data.isValid then
        data.add = add
        data.total_add = data.total_add + add
        if add > data.max_add then
            data.max_add = add
        end

    else
        if not data then
            data = {}
            lua_mem_sample[name] = data
        end
        data.isValid = true
        data.add = add
        data.total_add = add
        data.max_add = add
    end
    TablePool.ReleaseDeeper(stack, false)
end
function DumpLuaMemSample()
    local text = "";
    for k, v in pairs(lua_mem_sample) do
        if v.isValid then
            text = text .. " name = " .. k .. "," .. " add = " .. v.add .. "kb\r\n"
        end
    end
    if text ~= "" then
        logError(text)
    end
    for k, v in pairs(lua_mem_sample) do
        v.isValid = false
    end
    LuaGCMemory()
end
function LuaGCMemory(str)
    local isPrint = true
    local c1 = 0
    if isPrint then
        c1 = collectgarbage("count")
    end
    collectgarbage("collect")
    if isPrint then
        local c2 = collectgarbage("count")
        if not str then
            str = ""
        end
        log(str .. " before：" .. c1 .. ",after：" .. c2 .. ",release:" .. (c2 - c1))
    end
end

function MakeTopLevelId(level, phase)
    level = tostring(level)
    if phase ~= nil and tonumber(phase) ~= nil and tonumber(phase) > 0 and tonumber(phase) < 10 then
        if tonumber(phase) < 10 then
            phase = "0" .. tostring(phase)
        else
            phase = tostring(phase)
        end
        level = level .. phase
    else
        level = level .. "00"
    end
    return level
end
--[[
	渐入渐出效果
	obj:要渐隐的gameObject对象
	fade:渐隐的值 0为out 1为in
	frame:帧数
	timeScale:速度
	返回 utimerId
--]]
function FadeFuc(obj, fade, frame, callBackFunc,func)
    local _skeletonGraphic = obj:GetComponent("SkeletonGraphic")
    local count = 0;
    local timeAdd = 0;
    if fade==0 then
        timeAdd = 1
    end
    local utimerId = utimer.frameCall(function()
        if fade == 1 then
            timeAdd = timeAdd + (1 / frame);
        else
            timeAdd = timeAdd - (1 / frame);
        end
        count = count + 1;
        if count == frame then
            if fade == 1 then
                timeAdd = 1;
            else
                timeAdd = 0;
            end
        end
        if obj then
            if func then
                func(timeAdd)
            end
            _skeletonGraphic.color = UnityEngine.Color.New(_skeletonGraphic.color.r, _skeletonGraphic.color.g, _skeletonGraphic.color.b, timeAdd);
        end
        if count == frame then
            utimer.remove(utimerId);
            if callBackFunc then
                callBackFunc()
            end
        end
    end, frame)
    return utimerId;
end
function SetHeadFEWithFavorLv(head, favorLv)
    local feCtrl = head:GetController("FaverEffect")
    if feCtrl then
        if favorLv >= 10 then
            feCtrl.selectedIndex = 1
        else
            feCtrl.selectedIndex = 0
        end
    end
end

function SetHeadFEWithPartnerId(head, partnerId)
    local heroData = logicMgr.HeroManager.getHeroDatabyPartnerId(partnerId, true)
    local favorLv = (heroData or {}).favorLv or 0
    SetHeadFEWithFavorLv(head, favorLv)
end

function SetHeadFEWithModelId(head, modelId)
    local heroData = logicMgr.HeroManager.getHeroDatabyModelId(modelId, true)
    local favorLv = (heroData or {}).favorLv or 0
    SetHeadFEWithFavorLv(head, favorLv)
end

--获取globle表配置
function GetGlobalIntValue(id)
    local config = G.dataTable["t_global"][tostring(id)]
    if config then
        return config.f_int_value
    else
        return nil
    end
end

--获取globle表配置，tb = true 则转成table
function GetGlobalStringValue(id, tb)
    local config = G.dataTable["t_global"][tostring(id)]
    if config then
        if tb then
            return decodeJsonStrToTabel(config.f_string_value)
        end
        return config.f_string_value
    else
        return nil
    end
end

function CompareTable(a, b)
    local same = true
    if type(a) == "table" and type(b) == "table" then
        for k, v in pairs(a) do
            if v ~= b[k] then
                same = false
                break
            end
        end
    end
    return same
end

function GetFightVersion()
    local fightVersion = tonumber(G.dataTable["t_global"]["613"].f_string_value);
    return fightVersion;
end

-- 光耀值获取面板特殊展示：先展示光耀值，再展示获得物品
---@param rewards table 奖励列表
---@param closeCallback function 界面关闭回调
---@param flag boolean 透传参数
function ShowSpItemGetPanel(rewards, closeCallback, flag)
    local item_4s = {} -- 光耀石单独显示
    local items = {}
    for _, v in pairs(rewards) do
        if v.itemModelId == 4 then
            table.insert(item_4s, v)
        else
            table.insert(items, v)
        end
    end
    if #item_4s > 0 then
        if #items > 0 then
            showUI(PanelNames.UICommonItemGet, nil, nil, 3, item_4s, function()
                showUI(PanelNames.UICommonItemGet, nil, nil, 3, items, closeCallback, nil, flag)
            end)
        else
            showUI(PanelNames.UICommonItemGet, nil, nil, 3, item_4s, closeCallback, nil, flag)
        end
    else
        showUI(PanelNames.UICommonItemGet, nil, nil, 3, items, closeCallback, nil, flag);
    end
end

--- 校验英雄皮肤id，未开放的英雄替换为19（桃花）
---@param skinId number | string
---@return number | string
function checkHeroSkinId(skinId)
    if skinId == nil then
        return
    end

    local heroId = tostring(logicMgr.SkinManager.getHeroModelIdBySkinId(skinId))
    if not logicMgr.HeroManager.isOpenHeroAtlas(heroId) then
        return logicMgr.SkinManager.getHeroSkinId(19)
    end
    return skinId
end
--- 校验英雄id（配置表），未开放的英雄替换为19（桃花）
---@param heroId number | string
---@return number | string
function checkHeroModelId(heroId)
    if not logicMgr.HeroManager.isOpenHeroAtlas(heroId) then
        return 19
    end
    return heroId
end

--- 对比目标版本号，当前游戏包体是否更新过
--- @param targetVersion string 目标版本号
--- @return boolean 是否更新
function CompareOriginVersion(targetVersion)
    if not YanhuaMMO.AppConst.originVersion then
        return false
    end

    local targetVersionInt = Version2Int(targetVersion)
    local originVersionInt = Version2Int(YanhuaMMO.AppConst.originVersion)
    return originVersionInt >= targetVersionInt
end

--- 版本号转整形
--- @param version string 版本号
--- @return number
function Version2Int(version)
    if version == nil then
        return 0
    end

    local versions   = string.split(version, ".")
    local count      = #versions
    local versionInt = 0

    for i = 1, count do
        if i > 3 then
            versionInt = versionInt + tonumber(versions[i]) * (10 ^ (count - i))
        else
            versionInt = versionInt + tonumber(versions[i]) * (10 ^ (count - i + 3))
        end
    end
    return versionInt
end

--- 通过某一时间点获取时间戳
---@param futureDays number 天数：0代表的意思是当天，1是明天
---@param hour       number 小时：24格式的时间，传入2就是凌晨2点 默认0
---@param minute     number 分钟 默认0
---@param second     number 秒   默认0
---@return 时间戳
function GetFutureTime(futureDays, hour, minute, second)
    local curTimestamp = HelperFunc.GetServerTime() -- os.time()
    GetFutureTimeByTargetTime(curTimestamp, futureDays, hour, minute, second)
end

--- 根据指定时间获取某个时间后的时间戳
---@param target     number 指定时间戳
---@param futureDays number 天数：0代表的意思是当天，1是明天
---@param hour       number 小时：24格式的时间，传入2就是凌晨2点 默认0
---@param minute     number 分钟 默认0
---@param second     number 秒   默认0
---@return 时间戳
function GetFutureTimeByTargetTime(target, futureDays, hour, minute, second)
    hour   = hour or 0
    minute = minute or 0
    second = second or 0

    local curTimestamp = target
    local dayTimestamp = 24 * 60 * 60
    local newTime = curTimestamp + dayTimestamp * futureDays
    local newDate = os.date("!*t", newTime)
    return os.time({ year = newDate.year, month = newDate.month, day = newDate.day, hour = hour, min = minute, sec = second })
end
--[[
* 比率转换
*
* @return]]
function toRatio(ratio)
    return ratio / 10000.0;
end
function toRatioF(ratio)
    return ratio / 10000.0;
end

function fromRatio(ratio)
    return (ratio * 10000);
end

--[[/**
* 计算比率
*
* @param value
* @return
*/]]
function calcRatio(value, ratio)
    return (toRatio(ratio) * value);
end

function addTable(table1, table2)
    for i, v in pairs(table2) do
        table1[i] = v;

    end
end
function subList(tab, fromIndex, toIndex)
    local list = {};
    for i = fromIndex, toIndex do
        table.insert(list, tab[i]);
    end
    return list
end
function pairsByKeys(t)

    local a = {}

    for n in pairs(t) do

        a[#a + 1] = n

    end

    table.sort(a)

    local i = 0

    return function()

        i = i + 1

        return a[i], t[a[i]]

    end

end
--乱序
function Table_Rand(t)
    if t == nil then
        return
    end
    local tRet = {}
    local Total = table.getn(t)
    while Total > 0 do
        local i = math.random(1, Total)
        table.insert(tRet, t[i])
        t[i] = t[Total]
        Total = Total - 1
    end
    return tRet
end
--获取表所有key的数组
function getKeysTable(t)
    local list = {};
    for k, v in pairsByKeys(t) do
        table.insert(list, k);
    end
    return list;
end
function checkClass(table1, name)
    if type(table1) == "table" and table1.__cname == name then
        return true;
    end
    return false;
end
function Table_Rand(t)

    if t == nil then

        return

    end

    local tRet = {}

    local Total = table.getn(t)

    while Total > 0 do

        local i = math.random(1, Total)

        table.insert(tRet, t[i])

        t[i] = t[Total]

        Total = Total - 1

    end

    return tRet
end